В моем текущем проекте понадобилось кодировать медиа-файлы из любого формата в несколько определенных. Более того, мне нужно отслеживать статус процесса и отображать его пользователю. Я не хочу описывать, какие форматы нужны, и с какими проблемами я столкнулся при кодировании (может это будет в последующих заметках, если кого-нибудь заинтересует), здесь я расскажу общую идею реализации скриптов для кодирования и отслеживания прогресса.
Для начала, необходимо решить, как будет отображаться прогресс. Я не нашел ничего проще, чем вывод простого текста в стандартный поток вывода PROGRESS: 56, где 56 - это статус кодирования в процентах, и ERROR в случае ошибки. Каждый выходной формат будет обрабатываться отдельным скриптом (например, для получения видео для iPod будет скрипт ipod.rb).
Мне необходимо использовать две программы кодирования - mencoder and ffmpeg (не считая дополнительных инструментов вроде flvtool2 и других, так как их выполнение не занимает много времени). Это означает, что все, что мне нужно - это найти общий метод выполнения этих программ и обработки их вывода (который уже содержит информацию, достаточную для вычисления статуса кодирования).
Ну что ж, начнем. Для начала запустим mencoder:
Это очень сильно упрощенная команда, и я надеюсь, вы не будете использовать ее в реальных проекта
Как можно заметить, output.txt содержит строки, заканчивающиеся \r:
Я буду использовать метод Ruby IO.popen для того, чтобы разобрать вывод:
end
def execute_mencoder(command)
progress = nil
IO.popen(command) do |pipe|
pipe.each("\r") do |line|
if line =~ /Pos:[^(]*(\s*(\d+)%)/
p = $1.to_i
p = 100 if p > 100
if progress != p
progress = p
print "PROGRESS: #{progress}\n"
$defout.flush
end
end
end
end
raise MediaFormatException if $?.exitstatus != 0
end
Сначала я определил класс MediaFormatException, который будет использоваться для выбрасывания исключения для уведомления вызывающего кода об ошибках (мы поговорим об этом позже). Далее можно увидеть метод execute_mencoder, который принимает команду. Статус процесса будет выводиться в поток стандартного вывода, и прогресс не будет выводиться дважды.
Продолжим с ffmpeg:
Упс! Он производит вывод в поток ошибок!
Как можно заметить, он показывает текущий фрейм и время, а не проценты. Но в начале вывода выводится Duration: 00:00:24.9, потому мы можем высчитать прогресс самостоятельно:
Так сделаем же это!
progress = nil
IO.popen(command) do |pipe|
pipe.each("\r") do |line|
if line =~ /Duration: (\d{2}):(\d{2}):(\d{2}).(\d{1})/
duration = (($1.to_i * 60 + $2.to_i) * 60 + $3.to_i) * 10 + $4.to_i
end
if line =~ /time=(\d+).(\d+)/
if not duration.nil? and duration != 0
p = ($1.to_i * 10 + $2.to_i) * 100 / duration
else
p = 0
end
p = 100 if p > 100
if progress != p
progress = p
print "PROGRESS: #{progress}\n"
$defout.flush
end
end
end
end
raise MediaFormatException if $?.exitstatus != 0
end
Здесь мы использовали тот же класс MediaFormatException для исключений. Теперь у нас есть оба метода, можно начать тестирование.
command_ffmpeg = "ffmpeg -y -i input.avi -vcodec xvid -acodec mp3 -ab 96 output.avi 2>&1"
begin
execute_mencoder(command_mencoder)
execute_ffmpeg(command_ffmpeg)
rescue
print "ERROR\n"
exit 1
end
Обратите внимание, что поток ошибок перенаправлен в поток стандартного вывода, чтобы обработать статус процесса для ffmpeg.
Выглядит не очень красиво, поскольку нам нужно обрабатывать исключения в каждом скрипте. Напишем метод, который сделает это за нас:
yield
rescue
print "ERROR\n"
exit 1
end
И пример использования:
command_ffmpeg = "ffmpeg -y -i input.avi -vcodec xvid -acodec mp3 -ab 96 output.avi 2>&1"
safe_execute do
execute_mencoder(command_mencoder)
execute_ffmpeg(command_ffmpeg)
end
Просто, не правда ли? Теперь у нас есть простой текстовый статус процесса в стандартном потоке вывода, и нам не нужно обрабатывать вывод различных кодировщиков вручную. Теперь мы можем использовать этот скрипт как часть другого процесса, который будет отображать статус кодирования пользователю.
Русский
English
Интересная статья, добавляем в закладки. Спасибо!
Поддерживаю. Спасибо за статью. Хорошо, что ты нашел на нее время, которого у тебя щас нет. Тем не менее я жду еще одну про проблемы с кодированием.
to get the duration, change all the “d”s in the regex to “\d”s
Oh, thanks! Of course, you right. This is my syntax highlighter killed \
I have updated post.
Thanks again
[...] ffmpeg/mencoder for converting media [...]
I am using your method to process ffmpeg commands with images as my input, eg “ffmpeg -i %03d.jpg movie.mp4″ and I keep receiving errors that “%03d.jpg: I/O error occured
Usually that means that input file is truncated and/or corrupted.”
I know the images are good, I can issue the same exact command via bash (using the same images) and it builds the movie with no probs.
Of course, my first assumption was that the % is messing something up so I tried doing “ffmpeg -i ‘%03d.jpg’ movie.mp4″ but it did not change the out come any and I got the same error.
Any suggestions?
I just solved my previous problem. It was simply an issue of incorrect paths. Since my code is in a model it was trying to load images from my app/models/ directory. I just gave it a full path, eg
videodir = RAILS_ROOT + "/public/videos/"
"ffmpeg -i #{datadir}%03d.jpg #{videodir}mymovie.mp4"
For anyone else who may have this problem… it’s a silly little time consumer…
Yeah, you right. I’m using absolute path for input and output files too. Thanks for the notice.
[...] Encoding media files in Ruby using ffmpeg/mencoder with progress tracking (tags: ruby multimedia programming) [...]
Хорошая статья, только у меня вопрос, а как заставить ffmpeg работать с xvid-ом?
у меня FreeBSD и на команду вроде
говорит:
хотя ‘xvid’ установлен и mencoder с ним прекрасно работает.
Что делать?
Наиболее вероятное объяснение — ffmpeg собран без поддержки xvid (ключ --enable-xvid). Скорее всего придется его пересобрать.
Посмотреть список форматов, поддерживаемых ffmpeg:
При запуске ffmpeg выводятся ключи, с которыми он собран.
Спасибо за ответ, но не получается!
При установке из портов:
make config
No options to configure
если в ручную устанавливать:
./configure –help
особо выбора не предоставляет, не говоря уже об опции –enable-xvid
версия ffmpeg-0.4.9-pre1.tar.gz
в связи с этим другой вопрос, я решил использовать ffmpeg только потому что он делает нормальную копию звука (-acodec copy), в отличие от mencoder-а, который при опции -oac copy из дорожки “Dolby AC3 48000Hz 6ch 448Kbps” делает “DTS 48000Hz stereo 768Kbps” (так говорит Media Player Classic, приблизительно тоже и VirtualDub).
Может подскажешь как решить эту проблему?
Вот тут уж сорри, действительно не знаю, чем помочь. Никогда не работал с ffmpeg из портов фри. Как-то привычнее скачать официальные исходники и поставить куда-нить в /opt, чтобы не размазывались по системе.
С проблемой AC3 -> DTS тоже не сталкивался. У меня обычно задача из видео нормального качества сделать говно типа flv или mp4 с минимальными параметрами…
Hi, i tried to implement your Script, but it will not work for me. I get an Error: undefined method `execute_mencoder’ for main:Object (NoMethodError)
Why?
end
command_mencoder = "/usr/bin/mencoder /home/download/medien_trailer.mov -o /home/robin/download/output.avi -oac lavc -ovc lavc -lavcopts vcodec=xvid:acodec=mp3"
command_ffmpeg = "/usr/bin/ffmpeg -y -i /home/download/medien_trailer.mov -vcodec xvid -acodec mp3 -ab 96 /home/robin/download/output.avi 2>&1"
begin
execute_mencoder(command_mencoder)
execute_ffmpeg(command_ffmpeg)
rescue
print "ERROR\n"
exit 1
end
def execute_mencoder(command)
progress = nil
Open3.popen3(command) do |pipe|
pipe.each("\r") do |line|
if line =~ /Pos:[^(]*(\s*(\d+)%)/
p = $1.to_i
p = 100 if p > 100
if progress != p
progress = p
print "PROGRESS: #{progress}\n"
$defout.flush
end
end
end
end
raise MediaFormatException if $?.exitstatus != 0
end
def execute_ffmpeg(command)
progress = nil
Open3.popen3(command) do |pipe|
pipe.each("\r") do |line|
if line =~ /Duration: (\d{2}):(\d{2}):(\d{2}).(\d{1})/
duration = (($1.to_i * 60 + $2.to_i) * 60 + $3.to_i) * 10 + $4.to_i
end
if line =~ /time=(\d+).(\d+)/
if not duration.nil? and duration != 0
p = ($1.to_i * 10 + $2.to_i) * 100 / duration
else
p = 0
end
p = 100 if p > 100
if progress != p
progress = p
print "PROGRESS: #{progress}\n"
$defout.flush
end
end
end
end
raise MediaFormatException if $?.exitstatus != 0
end
Baerz, you should define methods execute_mencoder and execute_ffmpeg before executing them, for example, right after definition of the class MediaFormatException.
Hey, works
Thanks…
[...] going with a different guide [...]
[...] Encoding media files in Ruby using ffmpeg/mencoder with progress tracking [...]
Помогите решить вот такую проблему:
конвертирую flv в mov
на входе 8 Мб
на выходе 51 Мб, т.е. в 6 раз больше.
команда такая:
ffmpeg -i sqlintro1.flv -sameq 01sql.mov
Подскажите, если не затруднит, какими ключами можно уменьшить размер выходного файла, чтобы он был примерно таким же как и входной.
Hi , Thanks for this tutorial.
I am on leopard FFmpeg version SVN-r9102, ruby 1.8.6.
The above code works fine for me but the progress wont update sequentially as expected.
Any idea what this could be?? Is it something to do with pipe.each(”\r”) ??
Hi, do you know this library?
It’s very handy.
http://0xcc.net/ruby-progressbar/index.html.en
The ffmpeg progress counter will never update with Ruby 1.8.6 because duration is defined out of scope (the first if clause) when processing each line from ffmpeg’s output (the second if clause) — to fix it, change the line: “progress = nil” to “duration = progress = nil” and it will work fine.
Hi,
I’m trying to do something similar to what you did.
One thing I’m trying to add now is a timeout, what I would like to do is to check if I get some output from mencoder, if I don’t get any output on the stdour or error for more that 30 sec I want to quit the process.
Is that possible?
thanks very much in advance