Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

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

Anna | 16.06.2014 | нет комментариев
Прогр — не для политики. В данной статье рассматриваются экстраординарно технические аспекты реализации определенного программного решения. Для общего блага просьба отказаться от каких-либо политических дебатов, выступлений, агитации и тому сходственных действий в комментариях. Помимо того, просьба не использовать полученные познания в деструктивных целях, не начинать бекапить каждый видеоархив без специальной потребности и так дальше. Спасибо.

8 сентября, цельный день голосования. В этом году широкой общественности предлагается отслеживать через интернет за выборами столичного главы администрации города. Ряд граждан считают для себя увлекательным записать картинку с камер: у кого-то политически мотивированный интерес, а большинству легко тривиально любознательно посмотреть на себя и знакомых глазами Интернета. Данная статья призвана продемонстировать тезисы работы нынешней системы и предложить работающие концепты.


Со времен прошлых выборов система немножечко изменилась (напротив и статьи бы не было), следственно вначале припомним, как всё работало прежде и как стало трудиться теперь. Выходит, у всякой камеры есть неповторимый uid и пул серверов, с которых сыпется видео. Сформировав с применением этих данных специальный запрос, дозволено получить ссылку на ломтик видео, записанного выбранной камерой.

Для начала обнаружим данные обо всех существующих камерах. Мне показался особенно простым дальнейший метод: начнем поиск по номеру участка, с 1 до 3800. Для этого отправим GETvybory.mos.ru/json/id_search/aaa/bbb.json, где bbb это uid, а aaa это len(bbb). Скажем,vybory.mos.ru/json/id_search/1/3.json

Получим json с информацией об этом участке, что-то как бы вот этого:

[{"id":7933,"name":"Участок избирательной комиссии №3","num":"3","location_id":1162,"address":"Новейший Арбат, 36/9","raw_address":"г.Москва, Новейший Арбат ул., дом 36/9","is_standalone":false,"size":null,"location":{"id":1162,"address":"Россия, Москва, улица Новейший Арбат, 36/9","raw_address":"г.Москва, Новейший Арбат ул., дом 36/9","district_id":1,"area_id":null,"sub_area_id":null,"locality_id":1,"street_id":1590,"lat":55.753266,"lon":37.577301,"max_zoom":17}}]

Специальный интерес тут представляет id. Отправим GET вида vybory.mos.ru/account/channels?station_id=id, в данном случае vybory.mos.ru/account/channels?station_id=7933

В результате получим строчку с кракозяблами, на которые ругается мой редактор, но содержащие внутри хеши камер и адреса серверов. Выдерем оттуда хеши регуляркой вида
$([0-9a-h]{8}-[0-9a-h]{4}-[0-9a-h]{4}-[0-9a-h]{4}-[0-9a-h]{12}) и ip адреса регуляркой вида .*?(d{1,3}.d{1,3}.d{1,3}.d{1,3})

В итоге получим требуемую информацию о камерах нынешнего участка:
2e9dd8dc-edd4-11e2-9a6b-f0def1c0f84c 188.254.112.2 188.254.112.3 188.254.112.4
2ea32990-edd4-11e2-9a6b-f0def1c0f84c 188.254.112.2 188.254.112.3 188.254.112.4

Дальше начинаются ньюансы. Существует три типа камер: ветхие, новые и отсутствующие. Чем они отличаются я расскажу чуть позднее, вначале разберемся, как их различать, а различать их дюже легко — необходимо отправить GET вида http://SERVER/master.m3u8?cid=UID
Новая камера вернет кое-что как бы

#EXTM3U
#EXT-X-VERSION:2
#EXT-X-STREAM-INF:PROGRAM-ID=777,BANDWIDTH=3145728
/variant.m3u8?cid=e1164950-0c19-11e3-803b-00163ebf8df9&var=orig

Ветхая камера вернет что-то такого вида:

#EXTM3U
#EXT-X-MEDIA-SEQUENCE:136
#EXT-X-TARGETDURATION:15
#EXT-X-ALLOW-CACHE:NO
#EXT-X-PROGRAM-DATE-TIME:2013-09-04T12:05:40Z
#EXTINF:15,
/segment.ts?cid=2ea32990-edd4-11e2-9a6b-f0def1c0f84c&var=orig&ts=1378296340.93-1378296355.93
#EXTINF:15,
/segment.ts?cid=2ea32990-edd4-11e2-9a6b-f0def1c0f84c&var=orig&ts=1378296355.93-1378296370.93
#EXTINF:15,
/segment.ts?cid=2ea32990-edd4-11e2-9a6b-f0def1c0f84c&var=orig&ts=1378296370.93-1378296385.93
#EXTINF:15,
/segment.ts?cid=2ea32990-edd4-11e2-9a6b-f0def1c0f84c&var=orig&ts=1378296385.93-1378296400.93

Отсутствующая камера не вернет ничего, помимо 404 CID Was Not Found  :)

Сейчас, когда мы умеем получать информацию о камерах определенного участка, напишем многопоточную парсилку, которая соберет нам всю нужную информацию. Я выбираю складывать данные в безвозмездный монголаб, но абсолютно дозволено обойтись и обыкновенным shelve. Зная, что участков в москве 3500 , пробежимся циклом от 1 до 3800. Ниже набросанный на коленке, но тем не менее, работающий код. В нем, разумеется, необходимо вписать своё печенько и пароли от сервера монги.

# -*- coding: utf-8 -*-
import json, re
import httplib
import threading
from time import sleep
import Queue
from pymongo import MongoClient

client = MongoClient('mongodb://admin:кусь@кусь.mongolab.com:43368/elections')

db = client['elections']
data = db['data']

data.drop()

def get_data(uid):
    print uid
    headers = {'Origin': 'vybory.mos.ru',
    'X-Requested-With': 'XMLHttpRequest',
    'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0);',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'Accept': '*/*',
    'Referer': 'http://vybory.mos.ru/',
    'Accept-Encoding': 'deflate,sdch',
    'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4',
    'Accept-Charset': 'windows-1251,utf-8;q=0.7,*;q=0.3',
    'Cookie': 'rack.session=кусь'
    }

    try:
        conn = httplib.HTTPConnection('vybory.mos.ru')
        conn.request('GET', '/json/id_search/%d/%d.json'%(len(str(uid)), uid), None,headers)
        resp = conn.getresponse()
        try:
            content = json.loads(resp.read())[0]
            conn.request('GET', '/account/channels?station_id=%s'%content['id'], None,headers)
            resp = conn.getresponse()
            cont = resp.read()

            cnt=0
            for i in cont.split('x00')[1:]:
                cnt =1
                uid=re.findall(r'$([0-9a-h]{8}-[0-9a-h]{4}-[0-9a-h]{4}-[0-9a-h]{4}-[0-9a-h]{12})', i)[0]
                ip=re.findall(r'.*?(d{1,3}.d{1,3}.d{1,3}.d{1,3})', i)

                conn2 = httplib.HTTPConnection('%s'%ip[0])
                conn2.request('GET', '/master.m3u8?cid=%s'%(uid), None,headers)
                info = conn2.getresponse().read()
                conn2.close()
                if '/segment.ts' in info:
                    camtype='old'
                elif '/variant.m3u8' in info:
                    camtype='new'
                else:
                    camtype='nil'

                #print content
                data.insert({
                            'name':content['name'],
                            'num':content['num'],
                            'addr':content['address'],
                            'uid':uid,
                            'ip':ip,
                            'cnt':str(cnt),
                            'type':camtype
                            })

        except Exception,e:
            pass

    except Exception,e:
        print e
    conn.close()

queue = Queue.Queue()
def repeat():
    while True:
        try:
            item = queue.get_nowait()
        except Queue.Empty:
            break
        get_data(item)
        sleep(0.01)
        queue.task_done()

for i in xrange(1, 3800):
    queue.put(i)

for i in xrange(10):
    t = threading.Thread(target=repeat)
    t.start()
queue.join()

print data.find().count(),'all cams'
print data.find({'type':'nil'}).count(),'offline cams'
print data.find({'type':'old'}).count(),'old cams'
print data.find({'type':'new'}).count(),'new cams'

Сейчас у нас есть всецело собранная база камер. На момент написания статьи ветхих камер было 544, с ними, увы, получится трудиться только по-ветхому.
Но сейчас у нас есть и 5778 новых камер, и у них есть одна специфика. Чанки со ветхих камер через дюже короткое время протухают — необходимо непрерывно скачивать свежий плейлист, выдирать оттуда линки на чанки и качать их, пока не протухли. Новые камеры лишены этого недостатка. Дозволено качать чанки произвольных размеров за произвольный период времени, отправив GET вида http://SERVER/segment.ts?cid=UID&var=orig&ts=BEGIN-END Между BEGIN и END может быть не 15 секунд, а значительно огромнее. Я остановился на чанках продолжительностью 5 минут. На самом деле, дозволено указать хоть час, но в некоторых случаях, насколько я могу судить, если трансляция прерывалась в течение пределов чанка, не скачается каждый чанк. Дерзко говоря, если вы пытаетесь скачать 8 часов из архива чанками по часу и при этом в течение нескольких минут одного чанка трансляции реально не было, не скачается каждый часовой чанк. Следственно умно предпочесть чанк поменьше. Гуру алгоритмизации (которых, как мы помним, 10%) могут написать свой бинарный поиск, чтобы не пропало ни секунды видео =)
Кстати, чтобы закрыть вопрос — отсутствующей именуется камера, которая зарегистрирована в портале, но по факту не работает.

Автоматизируем процесс скачивания. Тут дозволено было сгородить свой многопоточный велосипед на питоне, но я решил воспользоваться сторонним софтом. Мы будем генерить метафайл со ссылками на чанки дляaria2c, метафайлы для tsmuxer и ступенчато их запускать.

Скажем, вот как-то так:

# -*- coding: utf-8 -*-
from time import sleep, time
from pymongo import MongoClient
import os
import subprocess
import shutil

#Корневая папка, куда будем складировать чанки
directory='e:/dumps'
#Размер чанка
delta=300
#Номер избирательного участка
num='666'

client = MongoClient('mongodb://кусь:кусь@кусь.mongolab.com:43368/elections')
db = client['elections']
data = db['data']

#Качать видео за последние 8 часов
start=int(time())-3600*8

#Создаем папку для дампов с избирательного участка
try:
    os.mkdir('%s/%s'%(directory,num))
except:
    pass

#Лезем в базу и достаем оттуда информацию о камерах с участка
for i in data.find({'num':num}):
    if i['type']=='nil':
        print 'Offline camera',i['uid']
    elif i['type']=='old':
        print 'Old camera',i['uid']
    else:
        print 'New camera',i['uid']
        f=open('links-%s-%s.txt'%(num, i['cnt']),'w')
        #Создаем поддиректории для всякой камеры
        try:
            os.mkdir('%s/%s/%s'%(directory,num,i['cnt']))
        except:
            pass

        cur=start
        files=''

        #Генерируем ссылки на чанки выбранной длины
        while True:
            if cur delta>time():
                for ip in i['ip']:
                    url = 'http://{0}/segment.ts?cid={1}&var=orig&ts={2}.00-{3}'.format(ip,
                                                                                   i['uid'],
                                                                                   cur, time())
                    f.write('%st'%url)
                f.write('n dir={0}/{1}/{2}n out={3}.tsn'.format(directory,num,i['cnt'],url[-27:]))
                files  = '"{0}/{1}/{2}/{3}.ts" '.format(directory,num,i['cnt'],url[-27:])
                break
            else:
                for ip in i['ip']:
                    url = 'http://{0}/segment.ts?cid={1}&var=orig&ts={2}.00-{3}.00'.format(ip,
                                                                                   i['uid'],
                                                                                   cur, cur delta)
                    f.write('%st'%url)
                f.write('n dir={0}/{1}/{2}n out={3}.tsn'.format(directory,num,i['cnt'],url[-27:]))

                files  = '"{0}/{1}/{2}/{3}.ts" '.format(directory,num,i['cnt'],url[-27:])

            cur =delta

        #Генерируем метафайл для склеивания чанков в один огромный файл.
        m=open('%s-%s.meta'%(num,i['cnt']),'w')
        m.write('MUXOPT --no-pcr-on-video-pid --new-audio-pes --vbr  --vbv-len=500n')
        m.write('V_MPEG4/ISO/AVC, %s, fps=23.976, insertSEI, contSPS, track=3300n'%files[:-1])
        m.write('A_AAC, %s, timeshift=-20ms, track=3301n'%files[:-1])
        m.close()

        f.close()
        subprocess.Popen('aria2c.exe -i links-%s-%s.txt -d %s -x 16'%(num, i['cnt'], directory), shell=True).communicate()
        subprocess.Popen('tsMuxeR.exe %s-%s.meta %s/%s-%s.tsn'%(num, i['cnt'], directory, num,i['cnt']), shell=True).communicate()
        shutil.rmtree('%s/%s'%(directory,num))
        os.remove('%s-%s.meta'%(num, i['cnt']))
        os.remove('links-%s-%s.txt'%(num, i['cnt']))

Вновь же, код писался экстраординарно в целях проверки концепта и не является примером соблюдения PEP8, но абсолютно работает. Скорость скачивания по внятным причинам зависит от многих факторов.

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB