Видеосервис своими руками и организация псевдостримминга
Интернет сервисы, Операционные системы, Программирование 03.06.2009
Сегодня я расскажу как работают «видеохостинги» и мы разработаем свой «видеосервис» для закачивания видео-файлов, с последующим конвертирование и созданием картинки для превью. Далее организуем показ во флеш-плеере и эмулируем стримминг (для перемотки на не загруженные части видео-файла).
Для всего этого нам понадобиться:
- Флеш-плеер для проигрывания видео-файлов — flowplayer
- Программа для конвертирования видео-файлов — ffmpeg
- Расширение PHP для получения превью-изображения — php-ffmpeg и библиотека для работы с графикой — GD
- Эмуляция потокового видео (псевдостримминг) с помощью утилиты yamdi, модуля для nginx — http_flv_module и плагина для flowplayer — flowplayer.pseudostreamming

Серверная ОСь по традиции — FreeBSD.
Установка необходимых нам утилит
ffmpeg
Для того чтобы конвертировать видео, нам необходимо установить утилиту ffmpeg. Есть еще аналогичная утилита — mencoder, тоже очень хорошая, но ffmpeg более популярен, поэтому именно его буду сейчас использовать. Эти утилиты с переменным успехом конкурируют между собой, поэтому также рекомендую посмотреть на mencoder в свободное время.
# make config
и выбираем LAME (lame MP3 codec)
Windows сборки ffmpeg:
yamdi
Далее, необходимо установить утилиту yamdi, которая вшивает мета-данные в видео-файл. Мета-данные необходимы для индексирования файла, то есть для того, чтобы можно было в еще не скачанном (не буферизированном) файле при перемотке перемещаться по индексам.
# make install
nginx
Теперь подошло время для nginx, который будет отдавать индексируемый (с помощью yamdi) видео-файл. На его месте может быть и lighttpd или еще какой-нибудь другой веб-сервер. Но я поддержу отечественного производителя! :)
# make config
и выбираем HTTP_FLV_MODULE, далее
Ну или просто при установке укажите опцию «--with-http_flv_module».
nginx-0.7.52. Теперь и под Windows:
Можно конечно использовать в качестве бекенда — PHP, например с помощью такого кода:
$start = (int) @$_GET['position'];
if ($start < 0) die("You fucking idiot");
// open file for reading
$fp = fopen($file, 'r+');
$fsize = filesize($file);
if ($start > 0)
{
// seek to requested position
fseek($fp, $start);
// FLV header for the movie part. Magic. Just trust me.
// Header code is completely taken from flv4php project
$header = "FLV" . pack('C', 1 ) . pack('C', 5 ) . pack('N', 9 ) . pack('N', 9 );
header("Content-Length: " . (strlen($header) + $fsize - $start));
echo $header;
} else {
header("Content-Length: " . $fsize);
}
set_time_limit(0);
while(!feof($fp)) {
print(fread($fp, 1024));
usleep(1000);// limit download speed
}
fclose($fp);Но мне удобнее использовать для этого модуль nginx'а, да и быстрее будет, имхо.
php5-ffmpeg
Это расширение PHP нам понадобиться для того чтобы получать информацию о видео-файле, а также для получения видео-фрейма для превью. Также необходимо установить библиотеку GD и сам PHP в частности.
# make install
php5-ffmpeg для Windows: http://pyha.ru/forum/attachment/1256/1317.0 и http://pyha.ru/forum/attachment/1257/1317.0
Можно конечно получать информацию путем обращения к
# ffmpeg -i /path/filename.flvи делать превью
# ffmpeg -i /path/filename.flv -an -ss ВРЕМЯ_ПОЗИЦИИ_В_СЕКУНДАХ -r 1 -vframes 1 -s 320x240 -y -f mjpeg /path/filename.jpgНо мне нравиться использовать расширение, это удобнее, имхо.
И перегрузите apache или nginx, смотря как вы используете PHP.
Конвертирование видео и получение изображения для превью
Для начала нам надо указать путь до исходного и результирующего файлов, прописать пути до превью-файлов, а также указать минимальную позицию фрейма для создания превью.
$inputPath = $_FILES['tmp_file'];
//Путь до результирующего файла, то есть отконвертированный и прошитый мета-данными видео-файл
$outputPath = "/path/video/filename.flv";
// Указываем минимальную позицию фрейма для захвата превью
$previewFrameMin = '3';
// Путь до превью-файла
$previewPath = "/path/preview/filename.jpg";
// Путь до мини-превью
$previewMiniPath = "/path/preview/filename_mini.jpg";
// Массив для сбора ошибок
$errorInfo = array();
Конвертируем исходный файл во временный, в формате FLV и разрешением 320×240, использую в качестве аудио-кодека mp3lame с частотой 44100, остальные настройки по умолчанию. Временный файл нужен для того, чтобы yamdi смог вшить мета-данные, так как он не может вшить в исходный файл.
* Конвертируем FLV во временный файл ($outputPath . '_temp').
*/
passthru(
'/usr/local/bin/ffmpeg -i ' . escapeshellarg($inputPath) .
' -f flv '.
' -s 320x240 '.
' -acodec libmp3lame '.
' -ar 44100 '.
escapeshellarg($outputPath . '_temp'),
$errorInfo['ffmpeg']
);
Прошиваем в видео-файл мета-данные для индексирования. Утилита yamdi написана на Си и работает очень быстро! А также удаляем временный файл.
* Прошиваем metadata (позиции ключевых фреймов) для перемотки
*/
passthru(
'/usr/local/bin/yamdi -i ' . escapeshellarg($outputPath . '_temp') .
' -o ' . escapeshellarg($outputPath),
$errorInfo['yamdi']);
/*
* Удаление временного файла, он нам уже не нужен...
*/
unlink($outputPath . '_temp');
Теперь получаем информацию о видео-файле и получаем объект фрейма для превью.
* С помощью расширения php5-ffmpeg получаем информацию о видео-файле
*/
$movie = new ffmpeg_movie($outputPath);
/*
* Вычисляем время воспроизведения видеофайла
*/
$duration = $movie->getDuration();
/*
* Вычисляем количество кадров
*/
$frameCount = $movie->getFrameCount();
/*
* Вычисляем позицию фрейма для его захвата
*/
$framePosition = ($frameCount > $previewFrameMin) ? $frameCount / 2 : $previewFrameMin;
/*
* Получаем объект фрейма для превью
*/
$preview = $movie->getFrame($framePosition);
А теперь получим и сохраним наши превьюшки.
* Записываем в файл - Превью (320x240)
*/
imagejpeg ($preview->toGDImage(), $previewPath);
/*
* Записываем в файл - Мини-превью (160x120)
*/
$previewMini = imagecreatetruecolor(160, 120);
$r = imagecopyresized(
$previewMini,
$preview->toGDImage(),
0, 0, 0, 0,
160, 120,
320, 240
);
imagejpeg($previewMini, $previewMiniPath);
Все, файл отконвертирован в FLV и прошит мета-данными, также созданы превью, теперь займемся выводом видео-файла — клиенту.
Плеер и показ видео
Почему flowplayer?
Сначала нам понадобится флеш-плеер, умеющий проигрывать видео в формате FLV и показывать картинки в формате JPEG (для превью).
Из всех аналогов (смотрите ниже, в «Полезные ссылки -> Видео-плееры») мне больше всего понравился , он:
- Кроссбраузерный и очень гибкий, и удобный в настройке
- Бесплатный для некоммерческого использования, а коммерческая версия стоит его возможностей!
- Имеет Flash и Javascript API, также поддерживает библиотеку JQuery (там доп. фишки какие-то)
- Имеет кучу полезных плагинов
- Умеет работать со стриммингом и псевдостриммингом
- Легко поменять шаблоны оформления и языковые шаблоны для настройки локализованного интерфейса
- Обширная документация, имеет достаточно большое сообщество в собственном форуме, а также оказывает бесплатную поддержку для коммерческой версии
- Поддерживает плейлисты и имеет полноэкранный режим
- Поддерживаемые форматы: FLV, SWF, MP3, MP4, H.264 video, JPG, PNG
- Сырцы открыты!

Остальное описание возможностей на официальном сайте:
Настройка flowplayer
С плеером мы определились, теперь надо его настроить для показа видео. В дистрибутив плеера входит:
- flowplayer-3.1.1.swf — сам плеер
- flowplayer.controls-3.1.1.swf — flash-controls
- flowplayer-3.1.1.min.js — это так называемый js-api
Для реализации псевдо-стримминга нам не хватает flowplayer.pseudostreaming-3.1.2.swf, который можно скачать и ознакомиться с использованием тут: .
Для работы со всеми видами стриминга есть титуриал:
Итак, приступим:
<script type="text/javascript" src="/swf/flowplayer-3.1.1.min.js"></script>
<!-- Настраиваем плеер -->
<script type="text/javascript">
flowplayer(
"player",
"/swf/flowplayer-3.1.1.swf",
{
playlist: [
{
url: 'http://example.com/preview/filename.jpg'
},
{
url: 'http://example.com/video/filename.flv',
autoPlay: false,
autoBuffering: false,
provider: 'pseudo'
}
],
plugins: {
pseudo: {
url: '/swf/flowplayer.pseudostreaming-3.1.2.swf'
}
}
}
);
</script>
<!-- DIV в который мы поместим плеер -->
<div style="width:416px;height:312px;display:block;padding:10px;" id="player"></div>
Все опции указанные в настройках можно посмотреть тут:
Настройка http_flv_module
Теперь настроим nginx на отдачу файла со смещением, для эмуляции стримминга.
flv;
root /path/video;
}
То есть, говорим nginx'у чтобы он для расширения «flv» использовал модуль http_flv_module и указываем в root путь до видео-файлов. Документация по http_flv_module:
Ну вот и все, теперь у вас полноценный «видеосервис»!
Полезные ссылки
ffmpeg
Работа с видео на php с помощью php ffmpeg
Видео-плееры



неплохой обзорчег. а как же мультидоменность, прогрессбар при загрузке и прочие вкусности?)))))))))))))
Мультидоменность обеспечивает коммерческая версия flowplayer :)
отлично, молодец!
пхп бекэнд это нечто))) работать будет только столько потоков вещания одновременно сколько воркеров в нгинкс-конфиге. про скорость я вообще молчу) так что не вариант)
Спасибо, хороший обзор!
>сколько воркеров в нгинкс-конфиге
или в апаче :)
Это кто-то реализовал уже??
Дима, что именно?
Неплохо.а эти бибиотеки позволяют работатьь с mp3 файлами ?
выдирать кусочки, менять битрейт и т.д.?
Сергей, ffmpeg умеет работать с mp3 и flowplayer тоже.
>выдирать кусочки, менять битрейт и т.д.?
да
как можно с вами связатьс лично?
Сергей, пройти в раздел сайта «Контакты», а по какому вопросу? Почему не в блог комментировать?
могу и в блог.
мне нужно разработать небольшой скриптик, желательно в связке с флеш, чтобы можно было вырезать кусок mp3 файла. уже неделю не могу найти кто бы мне помог...
Сергей, я во флеше не спец, а на серверной стороне надо просто передавать позиции и всё. Вся задача упирается во флеш.
ну а если без флеш?
какие варианты?, цены?, сроки? и самое главное — кто может сделать?
Сергей, что значит без флеш?
ну без флеш, значит без прослушивания и забиваем ручками в минутах и секундах откуда и до куда вырезаем...
>цены?, сроки? и самое главное — кто может сделать?
если я, то возьмусь за 2000—3000 руб (судя по заданию), в течении 2х дней смогу сделать
при этом либо должен быть сервер с ssh доступом и freebsd в качестве платформы, либо толковый админ, который все сам настроит что ему скажу
что могу сделать:
* вырезаю аудио по переданным параметрам секунд
* проигрываю обрезанное аудио
* смена битрейта
аську надобы для оперативности ;)
моя тридцать семь десять пять семь.
Сергей, у меня только жаббер и скайп
отписал вам на почту.
ок
Я вот что-то понять не могу... вот этот кусок
* Удаление временного файла, он нам уже не нужен...
*/
unlink($outputPath . '_temp');
/*
* С помощью расширения php5-ffmpeg получаем информацию о видео-файле
*/
$movie = new ffmpeg_movie($outputPath);
До этого мы НИГДЕ не копировали файлик в $outputPath,т.е. получается, что мы грохаем темповый файл и пытаемся создать «из ниоткуда» $outputPath-файл... или я что-то не понимаю?
Все — нашел... мои кривые глаза — ямди прошивает escapeshellarg ($outputPath)...
Алексей, угу )
Не знаю как и быть. У меня есть свой плеер, написаный на acstinscript 2. Думаю как бы мне туда запихнуть этот псевдостример... Ведь .as библиотека псевдострименга под 3-ий идет, а я в нем ни бум-бум((( Декомпильнул этот flowplayer — нифига там не понял. У гого есть какие мысли что мне делать7
Либо использовать flowplayer, либо учить AS3
Обнадежил(((... Т.е. других вариантов нет7 Неужели нет какой-нить недокументированного метода или еще какого-нить способа грузить видео с нужной метки как seek (5), например7 Просто чисто для этой задачки пока AS3 учить нет времени, т.к. он сильно отличается от AS2 как я посмотрел, а плеер мне нужен именно мой... там своя специфика и другие плеера не подойдут ну никак... Может хоть какая зацепка есть7 а...7
Я не специалист во Flash/Flex и не знаю язык AS
на в первом абзаце понял, что нужно чтобы на сервере был lighttpd. Что это такое7 А флеш-проигрыватель даже не придется трогать. Как я понял просто запрос будет не к file.flv, а file.flv?start=123, например и сервак пришлет видео с нужного места. Если я правильно понял — все не так уж и плохо и возможно даже этот flowplayer ковырять не придется. Тока что это за lighttpd, как его сделать, что прописать в var so = new SWFObject («flash/my_swf.swf», «movie», «100%», «100%», «9», «#ffffff»);... html страницы? Вроде что-то проясняется, но непонятно что конкретно.
лайти это веб-сервер, альтернативный nginx
если у вас будет работать с лайти без проблем, то и с nginx тоже будет
[code]
stenogramma
var so = new SWFObject («flash/psevdo_1.swf», «movie», «100%», «100%», «9», «#ffffff»);//Мой плеер)))
so.addParam («allowFullScreen», «true»);
so.addVariable («start_seek», «1»);//Стартовый параметр
so.addVariable ('autostart','true');//Тоже откуда-то возможно нужно
so.addVariable («file_name», «http://flowplayer.org/video/Extremists.flv»);//Беру с
so.addVariable («streamscript», «lighttpd»);//Это я вычитал с форумов всяких
so.write («flashcontent1»);
[/code]
Так вот когда я отсылаю с параметром start_seek=0 — все окейно работает. В моем плеере переменная start_seek прибавляется к переменной file_name и получается запрос типа «http://flowplayer.org/video/Extremists.flv?start=0» и видео грузится по этому пути. Как я понял — главное что запрос идет на сервак lighttpd, а должно быть именно то что нужно. и как я понял нужно передавать в GET-е переменную start. Но работает тока с start=0. Не знаю — вроде капаю в нужном направлении, но файл отказывается грузиться если start не равно 0. Мысли есть7
Я не работал с лайти, по идее все должно быть нормально
В строке so.addVariable («streamscript», «lighttpd»); я передаю переменную — как ее должен обработать плеер? Может в этом дело, что я никак эту переменную не обрабатываю во флеше?
попробую поискать на форумах... блин, инфы мало((( В основном рассматривают уже готовые плееры, а что в нем должно быть, чтобы работало нигде нет. Должно быть все просто ...myFlv.flv?start=10, а файл лежать на lighttpd, например. Но не пашет. Чего-то я не учитываю((
А какой для видео нужен хостинг? Т.е. должны ли быть какие-то особые характеристики — выделенный сервер и т.п. Есть два «обычных» хостинга — буфферизация происходит медленно, хотя ролики не очень большие, и смотрю их я один.
Max Folder, нужен установленный ffmpeg, yamdi, nginx (+mod_flv), php-ffmpeg. Но, можно обойтись только ffmpeg, а через скрипт, который я приводил в статье можно эмулировать mod_flv
adw0rd, это я понял. А технические характеристики сервера, толщина канала? Нужно ли что-то специфическое (например, аренда сервера за 10000 рублей в месяц), или я просто беру хостинг, условно говоря, за 200 рублей в месяц и радуюсь жизни?
Условно говоря — да, можете взять хостинг и радоваться жизни :) А там уже узнаете — подходит он вам или нет. У меня прекрасно работает на FirstFDS за 600 руб.
Извиняюсь, за очередной глупый вопрос. FirstFDS (хотя оно где-то упоминается) я не нашел, нашел firstvds.ru — это оно? Виртуальный выделенный сервер.
Max Folder, да, именно FirstVDS, извините, с просони не то написал :/
Друзья, подскажите — какие ключи у хука watermark??? что-то не могу в инете найти нормальное описание. И работает ли корректно watermark.so с водяными знаками в формате png?
Алексей, вам
Я конвертирую видео используя Fedora+mencoder+On2VP6. Качество просто ураган. Нарезку делаю напрямую с DVD iso файла. Качество практически без потерь при битрейте 700кБит.
Тестировал производительность на реальных серверах.
Один WindowsXP другой Fedora конфигурации идентичные. Конвертера настроены идентично.
В Windows XP в 5 потоков конвертации 1 фильм снимает часов так за 14.
В Fedora в 10 потоков конвертации 1 фильм снимает практически в риалтайме — 1.5-2 часа.
Вывод: под Linux-ом фильм конвертируется в 14 раз быстрее.
Здесь полностью подробно описано как настроить качественный HDTV-видеохостинг
Настраивал полностью по этой статье.
подскажите кто тестировал данный скрипт скриминга на php, он работает вообще?
и каким плеером передавать GET['position']; ??
я так понимаю тут есть какие-то своя реализация...?
===
$start = (int) @$_GET['position'];
if ($start < 0) die ("You fucking idiot");
// open file for reading
...
===
мне вот захотелось переписать его на perl (или на Си) :)
зашел сюда , там увидел скрипт стримминаг очень навороченный и большой...
ЗЫ: вариант в этой статье работает?
да, и еще стояла задача:
конкретный файл отдавать конкретному пользователю у которого есть права...
то тут настроить внутрений редирект на nginx со стримингом...
ngx_http_flv и X-Accel-Redirect
а как эти два модуля связать? нужно сделать внутриний редирект на файл? и он начнет скачиваться, правильно? но а как смтриминг сюда можно прикрутить...?
вот есть дискусия
но я там не понял, как эти два модуля связать?
dima,
Вот отсюда взят код (ссылку приводил в конце статьи)
я его не проверял, ибо статья не о нем, это просто вариант. А в статье рассказывается именно о nginx модуле
Да, задачу вашу понял... Подумаю на досуге, сразу не могу ничего сказать :/
поняно, тут они написали
что переделали плеер из flv4php, НО не написали как переделали
вся проблема плеер прикрутить...
«Не меньшая благодарность – Willyam Bradberry (willyam[at]newmail[dod]ru) – за работу над нашей версией плеера (видоизмененный плеер из flv4php).»
подскажите, почему нельзя поставить просто файл с метанными и через JavaScript подгрузить flash?
есть ли у кого-то такой вариант на JS? а то не поулчается вот пробовал 100 раз:
1)
<div id='mediaspace'>This text will be replaced</div>
<script type='text/javascript'>
var so = new SWFObject('http://localhost:3000/fplayer/player-viral.swf','ply','470','320','9','#ffffff');
so.addParam('allowfullscreen','true');
so.addParam('allowscriptaccess','always');
so.addParam('wmode','opaque');
so.addVariable('file','http://localhost:3000/fplayer/video.flv');
so.write('mediaspace');
</script>
http://www.longtailvideo.com/support/jw-player-setup-wizard?example=1
2)
<!-- Здесь мы включили в документ SWFObject -->
<script type="text/javascript">
var flashvars = {way:"http://lissyara.org.ua/fplayer/video.flv", swf:"http://localhost:3000/video/uflvplayer.swf", w:"500", h:"375",
skin:"greyblack", pic:"http://lissyara.org.ua/video/sample.jpg", autoplay:"0", tools:"2", q:"0", volume:"70", comment:"Название"};
// Здесь мы перечислили параметры flashvars настройки плеера
var params = {bgcolor:"#FFFFFF", allowFullScreen:"true"}
// Здесь мы указали в параметрах цвет фона flash-ролика
var attributes = {id:"uplayer_flv",name:"uplayer_flv"};
// Здесь мы указали в атрибутах идентификатор и имя объекта в документе
swfobject.embedSWF("http://lissyara.org.ua/video/uflvplayer.swf", "http://lissyara.org.ua/fplayer/video.flv", "500", "70", "9.0.0", "http://lissyara.org.ua/video/expressInstall.swf",
flashvars, params, attributes);
// Здесь мы запустили SWFObject для публикации flash-ролика в формате: путь к файлу, id блока для публикации, ширина, высота, минимальная
версия flash-плеера, путь к файлу экспресс-инсталяции, flashvars переменные, params параметры, attributes атрибуты
</script>
....
<div id=uplayer_flv>
<a href=http://www.adobe.com/go/getflashplayer>
<img src=http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif alt="Get Adobe Flash player" />
</a>
</div>