- Published on
监控辅助工具的实现过程(三)
- Authors
- Name
- 万码皆空
问题的关键是session的自动获取。不知道当时哪里来的懒劲儿,就是不想写代码,一心想找一个自动化工具(苹果的Automator和微软的Power Automate)来实现,最后发现这并不会比写点代码简单,只好放弃。
时间一长,又开始嫌麻烦了。刚巧不知在哪里看到了一个python包,叫ddddocr,用来识别验证码。拿来测试了一下效果很好。那不能浪费,用一下吧。
获取session
获取session需要三步:
- 使用手机号、密码、验证码登陆获取token
- 使用token获取可用摄像头播放地址
- 从任意播放地址中提取session
因为全家只有我一个账号能访问,所以手机信息我直接写死就可以了,代码如下:
import ddddocr
import requests
import time
# 获取验证码,同时需要从cookie中获取一个code_key
def get_verify_code():
t = time.time()
timestamp = int(round(t * 1000))
try:
response = requests.get(
"https://target.domain/verifyCode?t={}".format(timestamp))
code_key = response.cookies['CODE_KEY']
ocr = ddddocr.DdddOcr()
code = ocr.classification(response.content)
return (code.lower(), code_key)
except Exception as err:
return ('','')
# 获取token,使用
def get_token():
code, code_key = get_verify_code()
headers = {
"accept": "*/*",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"Cookie": "CODE_KEY={}".format(code_key),
"content-type": "application/json;charset=UTF-8"
}
body = '{{"username":"139xxxxxxxx","password":"12345678","verifyCode":"{}"}}'.format(code)
try:
response = requests.post("https://target.domain/user/login", headers=headers, data=body)
res = response.json()
success_code = res['code']
data = res['data']
if success_code == '000':
return data['token']
else:
raise Exception('获取token失败,错误代码:{}'.format(success_code))
except Exception as err:
return ''
# 获取session
def get_session():
token = get_token()
if len(token) == 0:
return ''
headers = {
"accept": "*/*",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"content-type": "application/json;charset=UTF-8"
}
body = '{{"token":"{}","camera_id":"camera_id001","childId":00000001}}'.format(token)
try:
response = requests.post("https://target.domain/camera/info", headers=headers, data=body)
res = response.json()
success_code = res['code']
if success_code == '000':
data = res['data']
return data['rtmpUrl'][-43:]
else:
raise Exception('获取session失败,错误代码:{}'.format(success_code))
except Exception as err:
return ''
以上代码隐藏了真实url和用户信息,调用get_session
就可以拿到session了。
代码在本地正常,可是放到服务器上,却一直出错。排查发现是对方服务器对ip做了限制,如果登陆时换了ip是需要短信验证码验证的。
那只好再处理下。增加了发送验证码和验证的功能。
import sys
import requests
from sessionmgr import get_token
# 获取验证码
def send_sms(token):
headers = {
"accept": "*/*",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"content-type": "application/json;charset=UTF-8"
}
body = '{{"token":"{}","mobile":139xxxxxxxx}}'.format(token)
try:
response = requests.post("https://target.domain/user/sms", headers=headers, data=body)
res = response.json()
except Exception as err:
print(str(err))
# 提交收到的验证码
def verify_sms(token,code):
headers = {
"accept": "*/*",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"content-type": "application/json;charset=UTF-8"
}
body = '{{"token":"{}","mobile":139xxxxxxxx,"verifyCode":"{}"}}'.format(token, code)
try:
response = requests.post("https://target.domain/user/verify", headers=headers, data=body)
res = response.json()
except Exception as err:
print(str(err))
# 获取命令行参数执行命令
if sys.argv[1] == 'token':
print(get_token())
elif sys.argv[1] == 'send':
send_sms(sys.argv[2])
elif sys.argv[1] == 'verify':
verify_sms(sys.argv[2], sys.argv[3])
else:
pass
将代码保存到sms_verify.py
,拷贝到服务器端执行如下命令:
# 获取token,用来发送验证码
python sms_verify.py token
# 使用上一步拿到的token发送验证码
python sms_verify.py send token_str
# 收集收到验证码后,提交
python sms_verify.py verify verify_code_str
验证成功后,服务器端就可以直接获取session了。
使用http服务
然后我们做一个获取session的http服务,这样就可以随时随地访问获取session了。
from http.server import HTTPServer, BaseHTTPRequestHandler
from datetime import datetime
import time
from sessionmgr import get_session
SESSION_EXPIRED_TIME = 30 * 60
class ResquestHandler(BaseHTTPRequestHandler):
__session = ''
__session_time = 0
def __get_session(self):
if time.time() - ResquestHandler.__session_time > SESSION_EXPIRED_TIME:
ResquestHandler.__session, ResquestHandler.__session_time = (
get_session(), time.time())
def do_GET(self):
try:
self.__get_session()
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(ResquestHandler.__session.encode('utf-8'))
except Exception as err:
self.__send_response(500, err)
def main():
server = HTTPServer(('', 8889), ResquestHandler)
server.serve_forever()
main()
启动后,我们就可以通过访问 https://my.domain:8889/ 获取session了。
优化转发服务
因为获取session有时间段限制,我们的转发服务还是不能停,这样在超出合法时间后我们也能查看被转发的摄像头。
我们先改一下nginx配置,去掉execstatic指令,这样nginx就不需要重启了。
# rtmp服务配置
rtmp {
server {
listen 8888;
chunk_size 4000;
application live1 {
live on;
record off;
}
application live2 {
live on;
record off;
}
application live3 {
live on;
record off;
}
}
}
然后我们使用python的subprocess模块自己管理ffmpeg进程,当一个ffmpeg子进程出错时,就重新获取session,拼凑出播放地址,然后启动新的ffmpeg进程推送到我们的转发服务上,代码就不写了,推送命令如下:
ffmpeg -rw_timeout 30000000 -probesize 102400 -i "rtmp://xxxx.xxx/camera_id001?session=xxxxxxxxxxxx" -c copy -f flv rtmp://localhost:8888/live1
编写客户端脚本
接下来,我们编写客户端播放脚本:
import subprocess
import shlex
import sys
import requests
from help import print_help
CMD_TEMPLATE = 'ffplay rtmp://xxxx.xxx/{}?session={} -analyzeduration 500'
CMD_LIVE = 'ffplay rtmp://{}:8888/{} -analyzeduration 500'
def main():
# 打印帮助信息
if sys.argv[1] == 'help':
print_help()
# 如果启动参数以live开头,则请求转发服务
elif sys.argv[1].find('live') == 0:
cmd = shlex.split(CMD_LIVE.format(HOST, sys.argv[1]))
# 否则,直连对方直播服务
else:
resposne = requests.get('http://my.domain:8888/')
session = resposne.text
camera_id = 'camera_id{:0>3}'.format(sys.argv[1])
cmd = shlex.split(CMD_TEMPLATE.format(camera_id, session))
subprocess.run(cmd)
查看直播命令如下:
# 使用转发服务
python play.py live1
# 直连对方直播服务
python play.py 1
记录脚本与play类似,替换成ffmpeg命令即可。这样客户端就算制作完成了。
android
android系统自带的播放器组件不能解码这个视频格式,我们还需要使用ffmpeg。不过自己编译、集成非常麻烦,干脆直接修改ijkplayer的example吧。修改FileExplorerActivity.java
直接展示一个摄像头id列表,点击某个item时获取session拼凑出播放地址,然后播放。代码大同小异,就不贴了,直接上几个图吧。
ios
也是修改example。
总结
目前基本解决了我的需求,除了完全自动录制播放。其实自动获取session还不能根本性解决问题,因为过了关闭监控时间,就没办法获取session了。有的时候,孩子上兴趣班会到别的教室,这时候除了转发的三个监控,其他监控因为无法获得session已经没办法查看。要想进一步突破限制的话,要不把监控全部设置到转发服务器,要不想办法突破获取session的限制。
对方的session还是有一定规律的,通过摸索,心里大概有一个设想,可是我不想太深入去做,因为已经够用了,如果还有忍不住的一天那就再说吧。