把学校直播的电视节目保存到本地

时间:2020-12-08 15:55:57

        有时候想看电视节目,但播放的时候自己可能在开会在吃饭在厕上等等,经常错过好多电视节目。每个学校都有电视转播的网站,从那里定时下载,到时候观看就啥都不耽误了,关键还能快进哈~开始搞起~

        学校的电视直播地址是http://video.ustc.edu.cn/tv.php,比如用vlc看凤凰卫视:

cvlc rtsp://202.38.64.43/tv7
        打开wireshark截点包看看:


把学校直播的电视节目保存到本地

可以看到用的是RTSP协议,RTSP协议提供几个方法如下:

把学校直播的电视节目保存到本地

        非常简单,和HTTP协议类似,使用554端口。到这里,我还不知道直播的视频流是什么格式的,于是在截的包里接着找,发现DESCRIBE的响应包非常大,用了4个TCP分段才组成这个PDU,把这个PDU数据包取出来如下(太大了,截取重要部分):

AccRTSP/1.0 200 OK
		Content-Type: application/sdp
		Vary: Accept
		X-Playlist-Gen-Id: 2222430
		X-Broadcast-Id: 1989898
		Content-Length: 4756
		Date: Tue, 15 Oct 2013 03:40:28 GMT
		CSeq: 3
		Server: WMServer/9.6.7600.16564
		Supported: com.microsoft.wm.srvppair, com.microsoft.wm.sswitch, com.microsoft.wm.eosmsg, com.microsoft.wm.fastcache, com.microsoft.wm.packetpairssrc, com.microsoft.wm.startupprofile
		Last-Modified: Sat, 30 Dec 1899 00:00:00 GMT
		Cache-Control: x-wms-stream-type="broadcast", no-cache, no-user-cache, private

		v=0
		o=- 201310150103460649 201310150103460649 IN IP4 127.0.0.1
		s=TV Live10
		c=IN IP4 0.0.0.0
		b=AS:749
		a=maxps:8000
		t=0 0
a=control:rtsp://202.38.64.43/tv7/
a=etag:{19304D1E-37B9-8A6B-A6DB-2B3566BAFA98}
a=range:npt=5.000-5.000
a=type:broadcast
a=recvonly
a=pgmpu:data:application/x-wms-contentdesc,8,language,31,0,,44,WMS_CONTENT_DESCRIPTION_SERVER_BRANDING_INFO,31,12,WMServer/9.6,6,author,31,7,USTCNIC,5,title,31,9,TV%20Live10,51,WMS_CONTENT_DESCRIPTION_PLAYLIST_ENTRY_START_OFFSET,3,4,5000,47,WMS_CONTENT_DESCRIPTION_PLAYLIST_ENTRY_DURATION,3,1,0,58,WMS_CONTENT_DESCRIPTION_COPIED_METADATA_FROM_PLAYLIST_FILE,3,1,1,42,WMS_CONTENT_DESCRIPTION_PLAYLIST_ENTRY_URL,31,1,/%0D%0A
a=pgmpu:data:application/vnd.ms.wms-hdr.asfv1;base64,MCaydY5mzxGm2QCqAG

从最后一句可以看出,视频流格式就是ASF格式。后面有个base64,然后后面4个tcp包都是这样的字符,我猜是base64转码的头信息,结果证实了我的猜想~把base64后面的字符串解码成二进制文件(用linux自带的base64工具),如下:

把学校直播的电视节目保存到本地

这种ASF的视频格式我认为是落后的,所以没必要搞的很明白,能用就好。0x30 0x26就是ASF文件的开始,这个没错,果然是头信息。第二行前8个字节代表视频头的长度,这个地方很重要,我在这儿浪费了好长时间,它的长度不是0x94 0x0a 0x00 ... 0x00,而是翻过来,小端模式来看,和普通的TCP包段有所区别,很坑~所以它的包大小为:0x0a94,即2708字节。通过截取的数据包发现,SETUP发送两次,第一次先请求audio,同时获得sessionID,因为RTSP是有状态的协议,需要一个session来维持;第二次请求video,这时发送的请求包里必须带着sessionID。定义的代码如下:

import socket
import time
import re
values={
		'ip':'202.38.64.43',
		'port':554,
		'url':'rtsp://202.38.64.43/tv5',
		'userAgent':'LibVLC/2.0.8 (LIVE555 Streaming Media v2011.12.23)',
		}

def OPTIONS(url,seq,userAgent):
	msg='OPTIONS '+url+' RTSP/1.0\r\n'
	msg+='CSeq:'+str(seq)+'\r\n'
	msg+='User-Agent:'+userAgent+'\r\n'
	msg+='\r\n'
	print(msg)
	msg=msg.encode('utf8')
	return msg

def DESCRIBE(url,seq,userAgent):
	msg='DESCRIBE '+url+' RTSP/1.0\r\n'
	msg+='CSeq:'+str(seq)+'\r\n'
	msg+='User-Agent:'+userAgent+'\r\n'
	msg+='Accept:application/sdp\r\n'
	msg+='\r\n'
	print(msg)
	msg=msg.encode('utf8')
	return msg

def SETUP_audio(url,seq,userAgent):
	msg='SETUP '+url+'/audio'+' RTSP/1.0\r\n'
	msg+='CSeq:'+str(seq)+'\r\n'
	msg+='User-Agent:'+userAgent+'\r\n'
	msg+='Transport:RTP/AVP;unicast;client_port=40816-40817\r\n'
	msg+='\r\n'
	print(msg)
	msg=msg.encode('utf8')
	return msg
	
def SETUP_video(url,seq,userAgent,sessionID):
	msg='SETUP '+url+'/video'+' RTSP/1.0\r\n'
	msg+='CSeq:'+str(seq)+'\r\n'
	msg+='User-Agent:'+userAgent+'\r\n'
	msg+='Transport:RTP/AVP/TCP;unicast;interleaved=0-1\r\n'
	msg+='Session:'+sessionID+'\r\n'
	msg+='\r\n'
	print(msg)
	msg=msg.encode('utf8')
	return msg

def PLAY(url,seq,userAgent,sessionID):
	msg='PLAY '+url+' RTSP/1.0\r\n'
	msg+='CSeq:'+str(seq)+'\r\n'
	msg+='User-Agent:'+userAgent+'\r\n'
	msg+='Session:'+sessionID+'\r\n'
	msg+='\r\n'
	print(msg)
	msg=msg.encode('utf8')
	return msg

函数的使用如下所示:

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((values['ip'],values['port']))
seq=1
print('-------------OPTIONS----------------')
s.send(OPTIONS(values['url'],seq,values['userAgent']))
seq+=1
data=s.recv(1024*10)
message=data.decode('utf8')
print(message)
print(len(message))

print('-------------DESCRIBE----------------')
s.send(DESCRIBE(values['url'],seq,values['userAgent']))
seq+=1
data=s.recv(1024*10)
length=len(data)
print(length)
message=data.decode('utf8')
while True:
	data=s.recv(1024*10)
	tmp=len(data)
	message+=data.decode('utf8')
	if tmp<length:
		break
print(message)
print(len(message))

print('-------------SETUP_audio----------------')
s.send(SETUP_audio(values['url'],seq,values['userAgent']))
seq+=1
data=s.recv(1024*10)
message=data.decode('utf8')
print(message)
print(len(message))

rule=re.compile('Session:(.*);')
sessionID=rule.findall(message)
if len(sessionID)!=0:
	sessionID=sessionID[0]

print('-------------SETUP_video----------------')
s.send(SETUP_video(values['url'],seq,values['userAgent'],sessionID))
seq+=1
data=s.recv(1024*10)
message=data.decode('utf8')
print(message)
print(len(message))

print('-------------PLAY----------------')
s.send(PLAY(values['url'],seq,values['userAgent'],sessionID))
seq+=1
data=s.recv(1024*10)
message=data.decode('utf8')
print(message)
print(len(message))

f=open('video.asf','wb')
while True:
	data=s.recv(1024*10)
	print(len(data))
	f.write(data)

这个video.asf文件还是半成品,还需要把DESCRIBE的base64字符串解码成ASF文件头,拼接起来,这个很容易,关键这里面还有一个小问题,因为并不是把视频流直接放到每个TCP包中,而是每个TCP包还有一个可变长的包头,把它们去掉就是完整的视频,但是在我按位找每个位代表的意义的时候,我在bbs上的提问有了回答,感谢linux版主SoRSs的帮助,提供了rtspdump,用这个直接就可以搞起,那此时我的工作就显得毫无意义了哈~用法如下:

php rtspdump.php -r rtsp://202.38.64.43/tv5 -a tcp -o tv.asf

        现在的码农大多数都不是从0开始闭门造车,各种代码资源git clone一下如探囊取物,很多自己想的东西都是有大神们之前做过了,何不直接拿来用呢?如果真想研究细致一点可以再细究,如果不想研究,比如我对这个ASF格式就不太喜欢,内心总感觉是注定要淘汰的东西,所以我也只是为了下载视频而做这件事儿,但有现成的程序(虽然是php的),我也就没有再做下去的理由了,花时间在更感兴趣的事情上罢~哈哈,这是我的小感悟,以后一定先搞好调研再搞起!最后,再感谢下linux版主~


转载请注明:转自http://blog.csdn.net/littlethunder/article/details/12875339