Массовая запись с камер на выборах

в 19:11, , рубрики: python, выборы, метки: ,

Внимание. Хабр — не для политики. Пожалуйста, воздержитесь от обсуждения оной в комментариях.
В предверии первых выборов в России, в ходе которых все участки были оснащены веб-камерами, многие выражали желание записать для себя видео с камеры. Для этого предлагались разнообразные решения, от записи FRAPSом, до использования ffmpeg и так далее. Самым удачным, на мой взгляд, была утилита Qwertovsky, выложенная тут
В данном небольшом топике я хотел бы предложить свое решение и вкратце напомнить, как вообще работает вся эта система, благо что завтра состоятся парламентские выборы на братской Украине, за ходом которых любой желающий может наблюдать на сайте vybory2012.gov.ua.


Итак, что следует знать:
Для каждой камеры раз в 15 секунд генерируется свежий плейлист, в котором содержатся прямые ссылки на последние 4 кусочка видео, длительность каждого кусочка — 15 секунд. Соответственно, раз в минуту плейлист полностью обновляется, а чанки продолжают быть доступны еще некоторое время.
Плейлист дублируется на нескольких серверах, доступен по ссылке вида http://server/variant.m3u8?cid=uid&var=orig и выглядит он примерно так:

#EXTM3U
#EXT-X-MEDIA-SEQUENCE:6885
#EXT-X-TARGETDURATION:15
#EXT-X-ALLOW-CACHE:NO
#EXT-X-PROGRAM-DATE-TIME:2012-10-27T11:02:08Z
#EXTINF:13,
/segment.ts?cid=f0ffd596-aaa6-4601-9432-70d717dd666a&var=orig&ts=1351335728.24-1351335741.20
#EXTINF:11,
/segment.ts?cid=f0ffd596-aaa6-4601-9432-70d717dd666a&var=orig&ts=1351335741.20-1351335752.28
#EXTINF:11,
/segment.ts?cid=f0ffd596-aaa6-4601-9432-70d717dd666a&var=orig&ts=1351335752.28-1351335763.40
#EXTINF:14,
/segment.ts?cid=f0ffd596-aaa6-4601-9432-70d717dd666a&var=orig&ts=1351335763.40-1351335776.92
#EXTINF:11,
/segment.ts?cid=f0ffd596-aaa6-4601-9432-70d717dd666a&var=orig&ts=1351335776.92-1351335788.36

Ссылка вида /segment.ts?cid=f0ffd596-aaa6-4601-9432-70d717dd666a&var=orig&ts=1351335728.24-1351335741.20 в данном случае обозначает кусочек видео с камеры f0ffd596-aaa6-4601-9432-70d717dd666a и интервалом с 1351335728.24 по 1351335741.20 в несколько усложненном формате Unix time.
Периодическое, раз в минуту, распарсивание плейлиста и скачивание всех доступных кусочков даст нам в результате максимум информации с камеры, за исключением тех моментов, когда на стороне камеры будет исчезать доступ в Интернет. Например, как-нибудь так:

# -*- coding: utf-8 -*-
# vybory2012 (Proof of concept), yegorov-p
import urllib
import os
from time import strftime, localtime, sleep
import socket
import threading

#Адрес сервера syslog, куда сыпалась вся статистика
syslog_server='127.0.0.1'
syslog_port=514
#Корневая папка, куда будем складировать чанки
directory='dumps'

#Массив с id камер, их хешами и списком серверов, откуда можно тянуть чанки
cams=[
['563-1', "f0ffd596-aaa6-4601-9432-70d717dd666a",["82.207.0.3","82.207.0.3","82.207.0.3"]]
]

#обозначения уровней для сислога
LEVEL = {
	'emerg': 0, 'alert':1, 'crit': 2, 'err': 3,
	'warning': 4, 'notice': 5, 'info': 6, 'debug': 7
}

#процедурка, пишущая в сислог. Копирайты потеряны, сорри =(
def syslog(message, level=LEVEL['notice'], host=syslog_server, port=syslog_port):
	sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
	data = '<%d>%s' % (level + 24, message)
	sock.sendto(data, (host, port))
	sock.close()

#Основная процедура
def cam_rip(num,hash,servers):
    syslog('Recording cam %s at %s'%(hash, num), level=LEVEL['info'])
    try:
        os.mkdir('./%s/%s'%(directory,num))
    except:
        pass

    #Запускаем бесконечный цикл
    while 1:
        try:
            server=servers[0]
            #Скачиваем текущий плейлист
            page = urllib.urlopen("http://%s/variant.m3u8?cid=%s&var=orig"%(server,hash)).read()
            #Если плейлист кошерен, выдираем ссылки на отдельные чанки и скачиваем их
            if '/segment' in page:
                for i in page.split('n'):
                    if '/segment' in i:
                        filename=strftime("%d-%b-%H-%M-%S", localtime(int(i[-13:-3])))
                        f=open('./%s/%s/%s.ts'%(directory,num,filename),'wb')
                        #syslog('Chunk %s saved'%(filename), level=LEVEL['notice'])
                        f.write(urllib.urlopen("http://%s%s"%(server,i)).read())
                        f.close()
            else:
            #В противном случае ругаемся в сислог и делаем ротацию сервера
                syslog('No signal!Rotating server on cam %s at %s'%(hash,num), level=LEVEL['err'])
                servers.append(servers[0])
                del servers[0]
            sleep(60)
        except Exception,e:
            syslog('Error on cam %s: %s'%(hash,e), level=LEVEL['err'])
            servers.append(servers[0])
            del servers[0]

try:
    os.mkdir(directory)
except:
    pass

#Запускаем кучу потоков для каждой из камер
for i in cams:
    threading.Thread(target=cam_rip, kwargs={"num": i[0],"hash": i[1],"servers": i[2]}).start()
    sleep(1)
syslog('System started.', level=LEVEL['notice'])

Запускаем данный скрипт, указав ему папку для складирования чанков и, опционально, адрес сервера сислога, куда будет сыпаться всяческая текущая информация.
Однако, мы обошли вниманием один момент — откуда взять исходные данные для плейлистов, а именно сервер и id камеры? Здесь стоит сделать небольшое отступление. Данная система успешно применялась на выборах в России, в частности, я при помощи вышеупомянутого питонового скрипта смог записать все камеры своего города. Однако, с тех пор произошли некоторые изменения в движке. Раньше все необходимые данные можно было получить даже не логинясь. Теперь же требуется сначала добавить камеру себе в избранное, после чего по адресу /account/channels?station_id=cid (где cid — id камеры) станет доступен файлик с хешами камер и ip-адресами серверов. Где-то в районе двух часов дня я решил собрать полную базу хешей и выложить ее в паблик, но сервера уже сейчас, еще до основной нагрузке периодически вываливают 502 ошибку, что осложняет процесс =) Из 32183 участков хеши доступны примерно с 5000, и понемногу эта цифра увеличивается.
В принципе, текущие данные можно получить, открыв нужную камеру и выполнив CorePlayer.instances.core_player_1.source.origin в Firebug или его эквивалентах, но так получится получить только текущий сервер (а он будет отваливаться, судя по опыту российских выборов).

В данный момент на камерах показываются заглушки, видимо украинские коллеги учли российский опыт, и решили не показывать будни школ, библиотек и общежитий.

Автор: PEgorov

Поделиться

* - обязательные к заполнению поля