Понадобилось начальству в своё время организовать своими силами видео-наблюдение за некоторыми вещами и уложиться в минимальное финансирование. Задача автоматизировать это легла на плечи системного администратора, то есть – меня.
Дано: N – видео-камер D-Link 2102, физический двух-юнитовый сервер под сервер видео-наблюдения и удаленное файло-хранилище.
Результатом должна быть возможность пускать некоторых пользователей на сервер видеонаблюдения в онлайн режиме и организовать архив видеозаписей.
Под катом несколько скриптов, которые сильно помогли мне понять, как лучше писать код, зачем нужны многие вещи и как они решаются, а так же навести порядок в своей голове и очень надеюсь, что они помогут кому-то еще.
Проект писался достаточно сложно — настолько сильно я shell(bash) скрипты не изучал — не было надобности до этого.
Но, когда задача поставлена и в голове есть алгоритм решения — все скрипты были переделаны так, чтобы читая их через пол года у меня и моего преемника не было вопросов и желания переписать все с нуля.
UPD: пост периодически обновляется.
Дистрибутивом выбран Ubuntu-Server 10.04. Серверной частью для онлайн просмотра стал ZoneMinder.Как настраивать Ubuntu и ZoneMinder написано много документации, поэтому этот вопрос останется за пределами данного документа, мне бы хотелось рассказать про автоматизацию ведения архива.
Было решено, что в качестве основного приёмника записей с камер (минутные ролики, это логически оправданно с точки зрения минимизации потерь видеозаписей) будет сервер на котором крутится ZoneMinder. На сервере развернут Samba-server как оптимальный и простой способ решения приема видео с камер и связующее звено между камерами и файло-хранилищем.
Все файлы, касающиеся видео-архива, находятся в /home/ipcamera/camname.
/home/ipcamera/scripts – скрипты, выполняющиеся по расписанию в /etc/crontab раз в час.
/home/ipcamera/out – монтируемая с файло-хранилища шара, куда складывается обработанное видео.
В зависимостях у нас samba tools для монтирования раздела где хранится архив по самбе, и bc, как консольный калькулятор, плюс кодеки и avimerge.
Задачи:
1. Получить с камер видео (одноминутный ролик)
2. Слить 60 минутных роликов в один.
3. Выгрузить полученное часовое видео на файло-хранилище.
4. Удалить с сервера видео-наблюдения всё видео, которое старше 3х дней.
5. Периодически удалять из архива видео, которое старше 3х месяцев.
6. Дать доступ к архиву тому, кому надо и не дать тем, кому не надо.
На сетевом уровне камеры доступны для сервера видеонаблюдения, могут быть сконфигурированы через веб интерфейс, файло-хранилище так же доступно.
Все скрипты откомментированы по максиму.
В настройках камер выставляем настроенный в организации ntp сервер и время записи видео, к примеру с 9 до 18 в настройках самих камер.
Да, про качество записи я в курсе и этот вопрос не рассматривается в данной статье, как и законность таких само-сборных систем наблюдения.
Настройка Samba и Zoneminder тут тоже не рассматриваются, хотя без кусочка конфига Samba тут не обойтись, и он будет.
Первый конфиг — кусочек конфигурации планировщика.
Похоже, кое-где слететели отступы в форматировании, прошу меня простить
Пойдем по порядку:
Конфигурационный файл для всего этого безобразия.
Файлик с описанием функций, чтобы разгрузить скрипты, и унифицировать переменные
Файлик, выполняющий слияние минутных видеороликов с камер (этот параметр в камерах, к сожалению, изменить нельзя, но это целесообразно с точки зрения минимизации потерь при записи и обрыве линка между камерой и принимающим сервером)
Скрипт, выгружающий все обработанное видео на файло-хранилище.
Теперь – очистка сервера видеонаблюдения от файлов старее 3х дней
И на закуску
Дело в том, что иногда на камерах слетает дата и они начинают писать в 2010 год, но по времени создания файлов можно понять, когда же фактически они были созданы и переименовать их как надо, именно на эту процедуру меня пока не хватило.
про ресурсоемкость:
#uptime
17:49:27 up 2:21, 1 user, load average: 4.27, 4.25, 4.24
больше загружен процессор, чем память. это ZoneMinder старается.
По логам:
На порядка 10 камер сейчас время выполнения такое (HP Proliant DL560 G1, 1Gb памяти, Xeon 2х2188.804 MHz):
Создание файловой шары для каждой камеры
Кусочек конфига самбы:
В камерах сейчас настроен сервер по IP адресу и имя camname.
##TODO список.
1. Проверка, установлены ли нужные пакеты, возможно сборка в deb всего этого безобразия.
2. Мониторинг доступности камер и оповещение ответственных.
3. Локализация скриптов.
4. deb пакет и нормальный man/texinfo.
5. Наверное стоило бы убрать вывод многих сообщений в &> /dev/null но я с этим пока не разбирался.
6. Дописать код, ответственный за проверку зависимостей и запрос на установку оных (возможна связь с пунктом 4)
7. Поскольку самба интегрирована с доменом, возможно организовать доступ по логинам сотрудников, но поскольку камеры с доменными логинами не очень хорошо работают — есть ли смысл опять же.
8. Пока не реализован мониторинг камер на живость линка к ним (ping).
9. Пока не реализован мониторинг того, что камера сбросилась или просто не получила время по ntp и начала писать видео в 2010м году.
На последние два этапа — локализацию переменных на разные языки, нормальную документацию и пакет меня тоже не хватило, да и есть ли смысл, если фактически скрипты выполняются автоматически и на сервер я захожу хорошо если раз в месяц — когда в голове полностью созрел весь алгоритм работы, и соответственно проверки на ошибки, вмешательство оператора стало практически не нужным, сегодня у сервера был uptime в несколько месяцев, и была выполненная первая перезагрузка по обновлению ядра, samba и Kerberos, которые ответственны за привязку сервера к Active Directory.
Единственное, что меня смущает — переполнение раздела с почтой, потому как по хорошему весь вывод надо подавлять, кроме сообщений в log файл.
На этом спасибо, надеюсь я Вам помог.
С удовольствием приму рецепты по оптимизации скриптов и развитию идеи в дальнейшем.
Исходники доступны тут: на GitHub
Дано: N – видео-камер D-Link 2102, физический двух-юнитовый сервер под сервер видео-наблюдения и удаленное файло-хранилище.
Результатом должна быть возможность пускать некоторых пользователей на сервер видеонаблюдения в онлайн режиме и организовать архив видеозаписей.
Под катом несколько скриптов, которые сильно помогли мне понять, как лучше писать код, зачем нужны многие вещи и как они решаются, а так же навести порядок в своей голове и очень надеюсь, что они помогут кому-то еще.
Проект писался достаточно сложно — настолько сильно я shell(bash) скрипты не изучал — не было надобности до этого.
Но, когда задача поставлена и в голове есть алгоритм решения — все скрипты были переделаны так, чтобы читая их через пол года у меня и моего преемника не было вопросов и желания переписать все с нуля.
UPD: пост периодически обновляется.
Дистрибутивом выбран Ubuntu-Server 10.04. Серверной частью для онлайн просмотра стал ZoneMinder.Как настраивать Ubuntu и ZoneMinder написано много документации, поэтому этот вопрос останется за пределами данного документа, мне бы хотелось рассказать про автоматизацию ведения архива.
Было решено, что в качестве основного приёмника записей с камер (минутные ролики, это логически оправданно с точки зрения минимизации потерь видеозаписей) будет сервер на котором крутится ZoneMinder. На сервере развернут Samba-server как оптимальный и простой способ решения приема видео с камер и связующее звено между камерами и файло-хранилищем.
Все файлы, касающиеся видео-архива, находятся в /home/ipcamera/camname.
/home/ipcamera/scripts – скрипты, выполняющиеся по расписанию в /etc/crontab раз в час.
/home/ipcamera/out – монтируемая с файло-хранилища шара, куда складывается обработанное видео.
В зависимостях у нас samba tools для монтирования раздела где хранится архив по самбе, и bc, как консольный калькулятор, плюс кодеки и avimerge.
Задачи:
1. Получить с камер видео (одноминутный ролик)
2. Слить 60 минутных роликов в один.
3. Выгрузить полученное часовое видео на файло-хранилище.
4. Удалить с сервера видео-наблюдения всё видео, которое старше 3х дней.
5. Периодически удалять из архива видео, которое старше 3х месяцев.
6. Дать доступ к архиву тому, кому надо и не дать тем, кому не надо.
На сетевом уровне камеры доступны для сервера видеонаблюдения, могут быть сконфигурированы через веб интерфейс, файло-хранилище так же доступно.
Все скрипты откомментированы по максиму.
В настройках камер выставляем настроенный в организации ntp сервер и время записи видео, к примеру с 9 до 18 в настройках самих камер.
Да, про качество записи я в курсе и этот вопрос не рассматривается в данной статье, как и законность таких само-сборных систем наблюдения.
Настройка Samba и Zoneminder тут тоже не рассматриваются, хотя без кусочка конфига Samba тут не обойтись, и он будет.
Первый конфиг — кусочек конфигурации планировщика.
Похоже, кое-где слететели отступы в форматировании, прошу меня простить
#cat /etc/crontab 5 9-20 * * 1-6 root /home/ITC/ipcamera/scripts/split # Собираем часовые ролики 10 9-20 * * 1-6 root /home/ITC/ipcamera/scripts/upload # Загрузка обработанного видео на файло-хранилище 30 20 * * 1-6 root /home/ITC/ipcamera/scripts/clear # Очистка локального сервера от устаревшего видео 0 21 * * 6 root /home/ITC/ipcamera/scripts/archieve # Чистим архив. 0 23 * * * root rm /var/spool/nullmailer/queue/* # Чистим почтовую очередь каждый вечер – за пару дней mail spooler забивает раздел.
Пойдем по порядку:
Конфигурационный файл для всего этого безобразия.
#cat scripts.conf # Основной каталог с общими каталогами для видео с камер. camshare="/home/ipcamera/" # Расширение файлов filename="*.avi" # Текущая дата для процедуры слияния currentdate=`date +%Y%m%d"/"%H` # Путь каталога, к которому монтируется удаленно, по smb, файло-хранилище для выходных файлов. outfiles="out/" # Lock-файл, индикатор что программа уже запущенна. lockfile=`basename $0`".lock" # Файл журнала. logfile="logfile" # Временный файл tmpfile=`basename $0`".tmp" # Путь на удаленном сервере для монтирования локально upload_share_name="//filearchieve/web_camera_video" # Локальный путь для выгрузки upload_share_path="/home/ipcamera/out" # Имя пользователя для подключения к каталогу выгрузки upload_share_user="ipcamerauser" # Пароль upload_share_passwd="ipcamerapassword" # Команда монтирования share_mount_command="mount -t cifs" # Период хранения архива, в месяцах. Age=3
Файлик с описанием функций, чтобы разгрузить скрипты, и унифицировать переменные
#cat functions.sh # Библиотека функций для скриптов обработки видеонаблюдения. # ver 1.12. ### Общие переменные для всех скриптов вынесены в файл конфигурации и подключаются тут. . /home/ipcamera/scripts/scripts.conf # Запись сообщения в журнал log_msg() { echo `date +%Y"/"%m"/"%d" "%T`" Скрипт "`basename $0`": $1." >> $camshare$logfile } # Создание файла блокировки при запуске скрипта - флага о том что скрипт уже запущен и выполняется. lock_on() { # Проверка, есть ли файл блокировки, если да - запись в лог и выход. if [ -f $camshare$lockfile ]; then echo `date +%d"/"%m"/"%Y" "%T`" Присутствует файл блокировки, возможно $0 уже запущен? программа завершает работу." log_msg "Присутствует файл блокировки, программа завершает работу" exit else # Если файла блокировки нет - создаем. touch $camshare$lockfile echo `date +%d"/"%m"/"%Y" "%T`" Создаем файл блокировки" fi } # Удаление файла блокировки при окончании работы скрипта. lock_off() { if [ -f $camshare$lockfile ]; then # Удаляем файл блокировки. rm $camshare$lockfile echo `date +%d"/"%m"/"%Y" "%T`" Файл блокировки успешно удалён." else # Выводим сообщение о том, что файла блокировки уже нет. echo `date +%d"/"%m"/"%Y" "%T`" Файл блокировки не найден." fi } # Удаление временного файла после окончания работы скрипта. clear_tmp() { if [ -f $camshare$tmpfile ]; then # Удаляем временный файл. rm $camshare$tmpfile echo `date +%d"/"%m"/"%Y" "%T`" Временный файл $tmpfile удалён." else # Временный файл не существует. echo `date +%d"/"%m"/"%Y" "%T`" Временный файл $tmpfile не найден." fi } # Монтирование каталога для выгрузки. upload_share_mount() { # Проверка, примонтирован ли каталог для выгрузки. test=`mount | grep "$upload_share_path"` if [ "$?" -eq "0" ] ; then # Если примонтирован - ничего не делаем. echo "Хранилище $upload_share_path ужё подключено." else # Монтируем каталог выгрузки. `$share_mount_command $upload_share_name $upload_share_path -o user=$upload_share_user"%"$upload_share_passwd` # Проверка результата. if [ "$?" -eq "0" ] ; then # Проверка закончилась положительно. echo "Хранилище $upload_share_path только что было успешно подключено." else # Запись в журнал об отрицательном результате и выход. echo "ОШИБКА: Хранилище $upload_share_path не было подключено. Завершение работы." log_msg "ОШИБКА: Хранилище $upload_share_path не было подключено. Завершение работы." exit fi fi } # Отключение примонтированного каталога для выгрузки. upload_share_umount() { # Проверка, подключен ли каталог для выгрузки. test=`mount | grep "$upload_share_path"` if [ "$?" -eq "0" ] ; then # Результат положительный, отключаем. `umount "$upload_share_path"` echo "Хранилище $upload_share_path только что было успешно отдключено." else # Выводим сообщение, что каталог уже отключен. echo "Хранилище $upload_share_path уже отключено." fi }
Файлик, выполняющий слияние минутных видеороликов с камер (этот параметр в камерах, к сожалению, изменить нельзя, но это целесообразно с точки зрения минимизации потерь при записи и обрыве линка между камерой и принимающим сервером)
#cat split #!/bin/sh ## Скрипт выполняет слияние всех видеофайлов в каталоге за прошедший час. #ver 1.0. # Подключаем общую для всех скриптов библиотеку функций. . /home/ipcamera/scripts/functions.sh lock_on log_msg "Запущен" # Ищем все каталоги по шаблону, убирая из вывода выходные файлы и файлы текущего часа во временный файл. find $camshare -type d | awk {'FS="/"} {print"/"$2"/"$3"/"$4"/"$5"/"$6"/"$7"/"$8}' | grep -v '/$' | grep -v '$outfiles' | grep -v $currentdate > $camshare$tmpfile # Переходим в найденный каталог, где сортируем файлы, выполняем их слияние и удаляем исходный файл. for i in `cat $camshare$tmpfile` ; do cd $i ; [ -f $i.avi ] || avimerge -i `ls | sort` -o $i.avi ; rm -fr $i ; done clear_tmp log_msg "Завершён" lock_off
Скрипт, выгружающий все обработанное видео на файло-хранилище.
#cat upload #!/bin/sh . /home/ipcamera/scripts/functions.sh ## Скрипт выполняет загрузку файлов, прошедших слияние, в каталог внешнего сервера. #ver 2.2. lock_on log_msg "Запущен" upload_share_mount # Ищем все файлы по шаблону AA.avi, убирая из вывода каталоги, выходные файлы и текущую дату во временный файл. find "$camshare" -type f -name '[0-9][0-9].avi' | awk '{FS="/"} {print"/"$2"/"$3"/"$4"/"$5"/"$6"/"$7"/"$8}' | grep -v "/$" | grep -v "$outfiles" | grep -v "$currentdate" > "$camshare$tmpfile" # Копируем файлы с проверкой на присутствие каталога с названием города/датой - в случае отсутствия создаем каталог камеры и каталог с датой создания. for source in `cat $camshare$tmpfile` ; do city=`echo "$source" | awk -F / '{ print $5 }'` date=`echo "$source" | awk -F / '{ print $7 }'` fname=`echo "$source" | awk -F / '{ print $8 }'` [ -d "$camshare$outfiles$city" ] || mkdir "$camshare$outfiles$city" | echo "Каталог для $city создан." [ -d "$camshare$outfiles$city/$date" ] || mkdir "$camshare$outfiles$city/$date" | echo "Каталог $camshare$outfiles$city/$date создан." [ -f "$camshare$outfiles$city/$date/$fname" ] || cp "$source" "$camshare$outfiles$city/$date/$fname" done clear_tmp upload_share_umount log_msg "Завершен" lock_off
Теперь – очистка сервера видеонаблюдения от файлов старее 3х дней
#cat clear #!/bin/sh ## Скрипт, удаляющий все видеофайлы старше 3х дней локально на видеосервере. #ver 1.0 # Подключаем общую для всех скриптов библиотеку функций. . /home/ipcamera/scripts/functions.sh lock_on log_msg "Запущен" upload_share_mount # Основная программа offset="172800" # 3 полных суток в секундах.# TODO вывести это в общие переменные unixdate=`date +%s` # текущая дата в скундах. timediff=`echo "$unixdate"-"$offset" | bc` # разница между текущим временем в секундах и offset'ом. # Ищем все файлы по шаблону AA.avi, убирая из вывода каталоги, выходные файлы и файлы текущей даты во временный файл. find "$camshare" -name '[0-9][0-9].avi' | awk '{FS="/"} {print"/"$2"/"$3"/"$4"/"$5"/"$6"/"$7"/"$8}' | grep -v '/$' | grep -v "$outfiles" | grep -v `date +%Y%m%d` > $camshare$tmpfile # В цикле проходим построчно по временному файлу. for source in `cat "$camshare$tmpfile"` ; do city=`echo "$source" | awk -F / '{ print $5 }'` # выделяем в переменную "город". date=`echo "$source" | awk -F / '{ print $7 }'` # -"- "дата". fname=`echo "$source" | awk -F / '{ print $8 }'` # -"- "имя файла". filedate=`ls -l --time-style=long-iso "$source" | awk '{print $6}'` # дата файла. unixfiledate=`date +%s -d"$filedate"` # переводим дату файла в секунды. outfilename="$camshare$outfiles$city/$date/$fname" # формируем имя выходного файла. # Если файл уже выгружен на фтп сервер, удаляем его с сервера наблюдения, если он старше timediff'а, если нет - оставляем. if [ -f "$outfilename" ] ; then echo "$outfilename присутствует в хранилище." if [ "$unixfiledate" -lt "$timediff" ] ; then echo "$source удален." rm "$source" else echo "$source" "$unixfiledate пропущен." fi else echo "Файл $outfilename не существует." fi done # Удаляем пустые каталоги. find "$camshare" -type d -empty | grep 'video' | xargs rm -fr {} upload_share_umount clear_tmp log_msg "Завершен" lock_off
И на закуску
#cat archieve #!/bin/sh ## Скрипт, удаляющий все видеофайлы старше определённого периода на сервере видеоархива. #ver 1.0. # Подключаем общую для всех скриптов библиотеку функций. . /home/ipcamera/scripts/functions.sh lock_on log_msg "Запущен" upload_share_mount # Основная программа. Year=`date +%Y` Month=`date +%m` Old=`echo "$Month"-"$Age"|bc` find "$camshare" -type d -name '[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]'| grep "$outfiles" > $camshare$tmpfile # В цикле проходим построчно по временному файлу. for source in `cat "$camshare$tmpfile"` ; do ddate=`echo "$source" | awk -F / '{ print $7 }'` Y=`echo $ddate | head -c4` M=`echo $ddate | head -c6 | tail -c2` D=`echo -n $ddate | tail -c2` if [ $Y -eq `date +%Y` ] ; then # echo "$source создан в текущем году." if [ "$M" -le "$Old" ] ; then # echo "Каталог $source старый, можно удалять." rm -rf $source else echo "Имя каталога $source находится в пределах хранимого промежутка ($Age месяца), поэтому пропущен." fi else # echo "Каталог $source создан не в этом году и требуется его обработка вручную." # TODO : вставить проверку mtime и переименовывать, пока 2010 год не трогаем. echo $source >> $camshare/2010.log fi done #TODO вставить обработку и процедуру переименования каталога и его содержимого. upload_share_umount clear_tmp log_msg "Завершен" lock_off
Дело в том, что иногда на камерах слетает дата и они начинают писать в 2010 год, но по времени создания файлов можно понять, когда же фактически они были созданы и переименовать их как надо, именно на эту процедуру меня пока не хватило.
про ресурсоемкость:
#uptime
17:49:27 up 2:21, 1 user, load average: 4.27, 4.25, 4.24
больше загружен процессор, чем память. это ZoneMinder старается.
По логам:
На порядка 10 камер сейчас время выполнения такое (HP Proliant DL560 G1, 1Gb памяти, Xeon 2х2188.804 MHz):
2011/10/29 20:30:01 Скрипт clear: Запущен. 2011/10/29 20:30:11 Скрипт clear: Завершен. 2011/10/29 21:00:01 Скрипт archieve: Запущен. 2011/10/29 21:00:12 Скрипт archieve: Завершен. 2011/10/31 09:05:01 Скрипт split: Запущен. 2011/10/31 09:05:30 Скрипт split: Завершён. 2011/10/31 09:10:02 Скрипт upload: Запущен. 2011/10/31 09:10:16 Скрипт upload: Завершен.
Создание файловой шары для каждой камеры
#cat mkshare #!/bin/bash ## Скрипт создает каталог для новой камеры. # Version name: 2.0. # Все используемые функции описаны в подключенной библиотеке functions.sh. # Подключаем общую библиотеку. . /home/ipcamera/scripts/functions.sh lock_on log_msg "Запущен" # Если аргументов при запуске скрипта нет - выходим с ошибкой. if [ "$1" != "" ]; then mkdir "$camshare$1" # Создаем каталог для новой камеры chmod 777 "$camshare$1" # Меняем права доступа # Дописываем в конфиг-файл samba сервера нужные строки. echo -e "\n[$1]\n comment = $1\n browseable = yes\n path = $camshare$1\n printable = no\n guest ok = yes\n read only =no\n create mask = 0700" >> /etc/samba/smb.conf # Перезапускаем samba. service smbd restart # Выводим дополнительное напоминание. echo "!!! Не забудьте проверить наличие IP адреса камеры в Samba ACL, находится в начале секции с описаниями общих каталогов для камер, и перезагрузить samba сервер: sudo service smbd restart" log_msg "Общий каталог $1 успешно создан." else # Просим после имени скрипта ввести название каталога для камеры. echo "Введите имя каталога после $0." fi
Кусочек конфига самбы:
allow hosts = 10.0.0.1, 10.0.0.2 # сюда пишем айпишники всех камер и всех компьютеров сотрудников, которые имеют доступ к архиву. [camname1] comment = camname1 browseable = yes path = /home/ipcamera/camname1 printable = no guest ok = yes read only = no
В камерах сейчас настроен сервер по IP адресу и имя camname.
##TODO список.
1. Проверка, установлены ли нужные пакеты, возможно сборка в deb всего этого безобразия.
2. Мониторинг доступности камер и оповещение ответственных.
3. Локализация скриптов.
4. deb пакет и нормальный man/texinfo.
5. Наверное стоило бы убрать вывод многих сообщений в &> /dev/null но я с этим пока не разбирался.
6. Дописать код, ответственный за проверку зависимостей и запрос на установку оных (возможна связь с пунктом 4)
7. Поскольку самба интегрирована с доменом, возможно организовать доступ по логинам сотрудников, но поскольку камеры с доменными логинами не очень хорошо работают — есть ли смысл опять же.
8. Пока не реализован мониторинг камер на живость линка к ним (ping).
9. Пока не реализован мониторинг того, что камера сбросилась или просто не получила время по ntp и начала писать видео в 2010м году.
На последние два этапа — локализацию переменных на разные языки, нормальную документацию и пакет меня тоже не хватило, да и есть ли смысл, если фактически скрипты выполняются автоматически и на сервер я захожу хорошо если раз в месяц — когда в голове полностью созрел весь алгоритм работы, и соответственно проверки на ошибки, вмешательство оператора стало практически не нужным, сегодня у сервера был uptime в несколько месяцев, и была выполненная первая перезагрузка по обновлению ядра, samba и Kerberos, которые ответственны за привязку сервера к Active Directory.
Единственное, что меня смущает — переполнение раздела с почтой, потому как по хорошему весь вывод надо подавлять, кроме сообщений в log файл.
На этом спасибо, надеюсь я Вам помог.
С удовольствием приму рецепты по оптимизации скриптов и развитию идеи в дальнейшем.
Исходники доступны тут: на GitHub