...не привлекая ничьего внимания. Потребуется ZoneMinder, Home Assistant, и пара подручных инструментов.
Несколько лет назад у меня завелось хобби — я начал коллекционировать восходы. Тогда я поселился в доме, из которого открывался широкий вид на небо. Однажды, в очередной раз задержавшись за делами до восхода, я вышел на балкон и восхитился красоте. Записал пару минут на телефон, и на этом успокоился. Но в следующий раз ночью выставил на балкон экшн‑камеру. Так все и завертелось.
Восход — это очень красиво, но не любой. Красоту восходу придают облака, при достаточной плотной, но не сплошной, облачности восход становится уникальным, красивым и завораживающим.
Если восход был интересным, получившийся набор видеофайлов я закидывал в видеоредактор, на глаз обрезал начало и конец, склеивал, и ускорял в 100–200 раз. В итоге получался видеоролик на 1–2 минуты. Я скидывал его друзьям, иногда загружал на ютуб.
Довольно быстро стало понятно что дальше так продолжать не может. В этом меня напрягало две вещи:
необходимость незадолго до восхода вылезти на балкон, поставить камеру, включить ее, организовать питание;
долго возиться с файлами после.
Так как я эталоннейшая сова первый пункт напрягал даже поменьше, т.к. 4 утра для меня это ранний вечер.
Поэтому первый шаг в автоматизации состоял в написании скрипта для склейки и ускорения. То есть с помощью ffmpeg собрать много файлов в один, а потом ускорить его.
в нем ничего особого нету но пусть будет
!#/bin/sh
rm /users/user/sunrise/source/all.txt
for file in /users/user/sunrise/source/*
do
file2=$(basename $file)
echo "file $file2" >> /users/user/sunrise/source/all.txt
done
cat /users/user/sunrise/source/all.txt
echo -n "Выглядит верно? (y/n) "
read item
case "$item" in
y|Y) echo "Ввели «y», продолжаем..."
echo "Продолжаем"
now=$(date +"%d %m %Y")
filename="Восход $now.mp4"
cd /users/user/sunrise/source/
ffmpeg -f concat -i /users/user/sunrise/source/all.txt -c copy /users/user/sunrise/out/temp.mp4
ffmpeg -i /users/user/sunrise/out/temp.mp4 -filter_complex "[0:v]setpts=0.005*PTS[v]" -map "[v]" /users/user/sunrise/out/Восход_$(date +"%d-%m-%Y").mp4
rm /users/user/sunrise/out/temp.mp4
;;
n|N) echo "Ввели «n», завершаем..."
exit 0
;;
*) echo "Ничего не ввели. Выполняем действие по умолчанию..."
exit 0
;;
esac
Потом я задумался об автоматизации записи. Мне не хотелось просто вешать камеру на балкон, чтобы не вызывать ненужных вопросов, поэтому я начал изобретать какую то систему, которая будет высовывать камеру, записывать что надо, а потом прятать ее.
Я разработал сложную систему моторизованного штатива, состоящую из 30 с лишним напечатанных деталей, мотора, управляемой из Home Assistant (далее - HA) четырехканальной релейной платы управления. До сих пор горжусь этой титанической работой. Внутрь конструкции была встроена недорогая управляемая камера высокого разрешения.
моделька
Хотя, если подумать, особо гордиться нечем, так как вся эта конструкция оказалась крайне ненадежной. Камера не была стабилизирована, видео получались плохие. Китайская ноунейм камера хоть и обещала 4K, но давала плохие записи. Механизм подъема/спуска часто заклинивало. На самом то деле недостатки стали понятны еще в процессе разработки, но на тот момент уже было интересно - осилю до конца или нет, так что доделал уже из интереса. Ну и после пары пробных запусков поставил в угол, до сих пор там стоит.
Ну и тут мы плавно подошли к нынешней ситуации и к описанию текущей работающей реализации съемки восходов. Я по прежнему не хотел просто вешать камеру на балкон, поэтому напечатал жесткий корпус, и фиксированно направил его на восток. На всякий случай в корпусе предусмотрена возможность менять угол наклона и поворота, но реальной необходимости в этом нет, камера охватывает весь доступный небосвод.
Корпус
Внутри спрятана HiWatch DS-I450L. Долго выбирал ее, изучая ТТХ и примеры видео, и кажется не прогадал. Угол чуть больше чем нужно, цветопередача хорошая, особенно радует цветное ночное видение. И полноценная влагозащита - несколько замечательных восходов сопровождались ливнями.
Камера подключена витухой к домашней сети и питается по POE через инжектор. В этом месте начинается автоматизация - POE инжектор воткнут в управляемую из HA розетку. Таким образом, на данном этапе у меня появилась надежная камера, направленная в нужную сторону, и возможность включать или выключать ее программно.
Следующий шаг - а на что записывать. Посмотрев что есть из СПО для видеонаблюдения, выбрал практически стандарт - Zoneminder (далее - zm), ролс-ройс в мире опенсорсных систем видеонаблюдения) Конечно, в этом предложении есть снобизм избалованного коммерческими системами человека, но объективно говоря в zm действительно много возможностей, хотя с наскоку в них непросто разобраться.
Покопавшись в запасах, собрал простенький комп под видеорегистратор, поставил на него серверную убунту. Включать сервер буду по питанию (на найденной матери не было wake‑on‑lan), поэтому для надежности еще туда wathdog реле воткнул.
Zoneminder ставится на нее очень просто, несколькими командами. Не буду останавливаться на этом, все как в документации. Добавление камеры тоже не вызывает проблем, главное чтобы она поддерживала rtsp.
Важный момент — по умолчанию zm записывает видео кусками по 10 минут. Лучше поставить минуту, ниже объясню почему.
После настройки записи zm записывает в mp4 файлы все что попадает на камеру, если она включена. Итак, у нас есть что записывать, и на что. Пора автоматизировать запись, и тут собственно вступает home assistant.
Собственно, бОльшая часть задачи решена за меня — в HA есть встроенный объект Sun, на основании которого система рассчитывает точное время восхода, заката, сумерек и тому подобного. Осталось взять это время и использовать его.
Логика такая — ежедневно в полночь будем рассчитывать время старта записи из расчета восход минус полтора часа, и время завершения записи из расчета восход плюс семь часов. HA умеет такое делать.
Заводим несколько вспомогательных объектов класса дата/время - время старта записи, время завершения записи, и время включения камеры. Как оказалось, включать камеру надо немного заранее, чтобы она успела прогреться, и с нее испарилась влага, если она осела на защитное стекло вечером (да, такое бывает).
И еще пару вспомогательных объектов класса переключатель - Записывать восход, и начать конвертацию восхода.
Заводим автоматизацию, рассчитывающую времена.
код автоматизации
alias: Расчитать время старта записи
description: ""
trigger:
- platform: time
at: "00:00:00"
condition: []
action:
- service: input_datetime.set_datetime
metadata: {}
data:
datetime: >-
{{ (as_timestamp(state_attr("sun.sun", "next_rising"))-9000) |
timestamp_local }}
target:
entity_id: input_datetime.vremia_vkliucheniia_kamery_dlia_progreva
- service: input_datetime.set_datetime
metadata: {}
data:
datetime: >-
{{ (as_timestamp(state_attr("sun.sun", "next_rising"))-5400) |
timestamp_local }}
target:
entity_id: input_datetime.start_zapisi_voskhoda
- service: input_datetime.set_datetime
metadata: {}
data:
datetime: >-
{{ (as_timestamp(state_attr("sun.sun", "next_rising"))+21600) |
timestamp_local }}
target:
entity_id: input_datetime.konets_zapisi_voskhoda
- service: telegram_bot.send_message
data:
message: >-
Запланирована запись восхода. Начало - {{
(as_timestamp(state_attr("sun.sun", "next_rising"))-5400) |
timestamp_local }}. Конец - {{ (as_timestamp(state_attr("sun.sun",
"next_rising"))+21600) | timestamp_local }}
mode: single
Автоматизация запускается в полночь, рассчитывает три вспомогательных параметра, и отправляет в телегу уведомление о планировании.
Делаем еще две автоматизации, срабатывающие во время включения камеры и старта записи (при условии что включена запись восхода). Первая включает питание камеры (которая, напомню, включается управляемой розеткой, питающей poe инжектор). Вторая - включает розетку с сервером zoneminder. С ними все просто, даже не буду расписывать.
Третья автоматизация сложнее и вызывает больше событий. Срабатывать будет во время конца записи, и делать две вещи - выключать питание камеры, и переключать служебный переключатель "начать конвертацию восхода". А дальше начинается магия.
Наверное, у zm есть какое то api, которым можно вытаскивать записи, но поскольку у меня тут задача сугубо прикладная, а запись с камеры не постоянная, я не стал с ним разбираться. Проще оказалось взять сами файлы из хранилища. При непрерывной записи zm делит видео на файлы, складывая их в подпапки текущего дня в каталоге камеры. Поскольку у меня камера включается только на время записи восхода, можно принять за данность что все видеофайлы в папке текущего дня являются валидной записью. Дальше надо только составить список файлов, склеить их, и ускорить.
Уже после первых записей стало понятно, что у zm есть косячок. Ну или он был в моей конфигурации, но на тот момент я его не нашел. Если что то случилось во время интервала записи (который напомню по умолчанию составляет 10 минут), то файл‑кусочек окажется битым, и недописанным. Во первых он не будет корректно обрабатываться ffmpeg'ом. Если подсунуть в операцию concat битый файл то операция упадет. А вторых, что хуже, окажется пропущенным какое то время, и будет некрасиво. Поэтому выше я поставил интервал разбивки в одну минуту. В худшем случае я эту минуту потеряю, что при ускорении в пару сотен раз не сильно заметно. Ну и при составлении списка файлов можно тестировать видеофайлы на корректность, выполняя при ошибке попытку восстановления.
UPD: косяк нашел уже существенно позже — производительности системы в первой версии не хватало для нормального функционирования записи. После небольшого апгрейда проблема ушла. А функционал тестирования остался, но я сделал его отключаемым.
файлы записей в zm
Но сначала надо как то понять, что все — пора склеивать. Тут опять понадобится HA с его реквизитом «начать конвертацию восхода». Логика такая — каждые 10 минут обращаемся по api к HA, и смотрим на состояние этого реквизита. Если он выключен то ничего не делаем. А если включен, то делаем вывод что камера выключена, запись завершена. Выключаем его, и начинаем конвертировать. Наверное можно и как то попроще, например прямо с zm смотреть, доступна ли камера, но возможны перебои в сети или еще что то, так что я вынес логику решения наружу.
Для доступа к api понадобится долгосрочный токен. Его можно получить в параметрах текущего пользователя.
Итак, на сервере с zm создаем два скрипта.
Первый — проверка, не пора ли начинать конвертацию. Его пихаем в cron с интервалом 10 минут.
Скрипт, проверяющий не пора ли начинать конвертировать
#!/bin/bash
currentstate=$(curl -X GET http://ip_HA:8123/api/states/input_boolean.nachat_konvertatsiiu_voskhoda -H "Authorization: Bearer ______TOKEN________" | jq '.state' | sed 's/\"//g')
if [[ $currentstate = "on" ]]
then
echo "time2work"
curl \
-H "Authorization: Bearer ______TOKEN________" \
-H "Content-Type: application/json" \
-d '{"state": "off"}' \
http://ip_HA:8123/api/states/input_boolean.nachat_konvertatsiiu_voskhoda
/home/user/makev.sh
else
echo "relax"
fi
Сначала обращаемся к HA, авторизуемся токеном, получаем состояние элемента, и приводим его к виду on или off.
Если включено, то выключаем его и запускаем второй скрипт.
А иначе ничего не делаем.
Второй - собственно сборка и ускорение.
Скрипт по сборке и конвертации видеозаписей
#!/bin/bash
export today=$(date +"%Y-%m-%d")
export resultfolder="/home/user/sunrise"
export rootf="/var/cache/zoneminder/events/1"
export tempsource="/home/user/sunrise/source"
export filelist="/home/user/sunrise/all.txt"
export TOKEN='====TOKEN===='
export CHAT_ID='====CHATID===='
export checkfiles="no"
MESSAGE="начинаем конвертировать восход"
curl \
--data parse_mode=HTML \
--data chat_id=${CHAT_ID}} \
--data text="${MESSAGE}" \
--request POST https://api.telegram.org/bot${TOKEN}/sendMessage
rm $filelist
rm -f $tempsource/*
find $rootf/$today -name "*.mp4" | sort -V > $filelist
find $rootf/$today -name "*.mp4" -exec cp "{}" /home/klbr/sunrise/source/ \;
exit
if [[ "$checkfiles" == "yes" ]]
then
rm -f $tempsource/*
for file in $(cat $filelist)
do
# if [ffmpeg -v error -i $file -f null - 2>&1 | cat | grep -q -wi error]
ffmpeg -v error -i $file -f null - 2>&1
if [ $? -eq 0 ]
then
echo "found error in $file. repairing..."
filename="$(basename -- $file)"
ffmpeg -i $file -vcodec copy -acodec copy $tempsource/$filename
else
echo "file $file is ok. copying..."
cp $file $tempsource/
fi
done
rm $filelist
find $tempsource -name "*.mp4" | sort -V > $filelist
fi
sed -i 's/^/file /' $filelist
now=$(date +"%d.%m.%Y")
filename="/home/user/sunrise/sunrise_$now.mp4"
cd $resultfolder/
rm $resultfolder/temp.mp4
ffmpeg -f concat -safe 0 -i $filelist -c copy $resultfolder/temp.mp4
ffmpeg -i $resultfolder/temp.mp4 -filter_complex "[0:v]setpts=0.005*PTS[v]" -map "[v]" $filename
rm $resultfolder/temp.mp4
if [ -e $filename ]
then
/usr/bin/curl --upload-file "$filename" ftp://zm:'zm'@_ip_network_storage/SUNRISE/
MESSAGE="Сконвертирован и загружен восход. Файл sunrise_$now.mp4"
curl \
--data parse_mode=HTML \
--data chat_id=${CHAT_ID}} \
--data text="${MESSAGE}" \
--request POST https://api.telegram.org/bot${TOKEN}/sendMessage
sudo shutdown now
else
MESSAGE="Что то пошло не так при конвертации восхода"
curl \
--data parse_mode=HTML \
--data chat_id=${CHAT_ID}} \
--data text="${MESSAGE}" \
--request POST https://api.telegram.org/bot${TOKEN}/sendMessage
fi
Поясню коротко: определяем текущую дату, приводим ее в формат, используемый zm, заходим в папку сегодняшнего дня, и рекурсивно составляем список файлов.
Если в переменной на старте включено тестирование видео то все файлы перед склеиванием тестируем. Если файл в норме — копируем его во временную папку без изменений. Если файл с ошибкой то пытаемся его восстановить и копируем уже восстановленный.
Если тестирование выключено, то берем файлы прямо из хранилища, без промежуточного копирования.
Затем передаем список в ffmpeg, получаем большой файл с полной записью. Потом ускоряем его.
Затем проверяем существует ли файл с результатом. Если да, то считаем что конвертация успешна, закидываем получившийся файл на сетевое хранилище, шлем уведомление в телегу, и выключаем сервер. Если нет — то шлем уведомление, и больше ничего не делаем.
Ну вот так вот, с помощью простого советского HA и zoneminder получилось автоматизировать хобби.
Дальше моей задачей было, получив уведомление, зайти на сетевое хранилище, посмотреть запись, и если она достойна коллекции, сохранить, иначе удалить.
В общем то, цель достигнута, коллекция пополняется почти сама, но теперь можно сделать еще одну интересную штуку. Вы когда нибудь видели 64 восхода одновременно?
Наличие множества файлов, в которых какое то общее событие происходит в одну и ту же секунду относительно начала файла, позволяет это сделать.
Видео на ютубе
Остальные записи там же.
Может быть потом руки дойдут до автоматической оценки восхода и заливки на ютуб (хотя зачем буду нужен я?). И еще надо как то убрать эффект рыбьего глаза, вносимый камерой из за ее широкоугольности. С этим тоже может помочь ffmpeg но что то пока не получается.
Периодически, набирая эту статью, я задавался вопросом — а зачем. Вряд ли кто то прочитав будет записывать восходы или закаты (а было бы неплохо, такого контента не хватает). Но потом подумал — ну восходы восходами, но ведь много людей делают таймлапсы. Обычно все ограничиваются набором фоток, которые потом склеивают. Но ведь ускоренное видео это же гораздо лучше, верно? Ну вот, можно взять этот кейс и относительно просто его делать.