Skip to content

Commit f3bf473

Browse files
authored
Merge pull request #150 from jackyding2679/cos-live
Cos live
2 parents c37b7b2 + cccbdd8 commit f3bf473

File tree

3 files changed

+483
-1
lines changed

3 files changed

+483
-1
lines changed

qcloud_cos/cos_auth.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,43 @@ def __call__(self, r):
9292
logger.debug("request headers: " + str(r.headers))
9393
return r
9494

95+
class CosRtmpAuth(AuthBase):
96+
def __init__(self, conf, bucket=None, channel=None, params={}, expire=10000):
97+
self._secret_id = conf._secret_id
98+
self._secret_key = conf._secret_key
99+
self._token = conf._token
100+
self._anonymous = conf._anonymous
101+
self._expire = expire
102+
self._params = params
103+
if self._token:
104+
self._params['q-token'] = self._token
105+
self._path = u'/' + bucket + u'/' + channel
106+
107+
def get_rtmp_sign(self):
108+
# get rtmp string
109+
canonicalized_param = ''
110+
for k, v in self._params.iteritems():
111+
canonicalized_param += '{key}={value}&'.format(key=k, value=v)
112+
canonicalized_param = canonicalized_param.rstrip('&')
113+
rtmp_str = u"{path}\n{params}\n".format(path=self._path, params=canonicalized_param)
114+
logger.debug("rtmp str: " + rtmp_str)
115+
116+
sha1 = hashlib.sha1()
117+
sha1.update(to_bytes(rtmp_str))
118+
# get time
119+
sign_time = int(time.time())
120+
sign_time_str = "{start_time};{end_time}".format(start_time = sign_time - 60, end_time = sign_time + self._expire)
121+
str_to_sign = "sha1\n{time}\n{sha1}\n".format(time = sign_time_str, sha1 = sha1.hexdigest())
122+
logger.debug('str_to_sign: ' + str(str_to_sign))
123+
# get sinature
124+
signature = hmac.new(to_bytes(self._secret_key), to_bytes(str_to_sign), hashlib.sha1).hexdigest()
125+
logger.debug('signature: ' + str(signature))
126+
rtmp_sign = "q-sign-algorithm=sha1&q-ak={ak}&q-sign-time={sign_time}&q-key-time={key_time}&q-signature={sign}".format(
127+
ak=self._secret_id, sign_time=sign_time_str, key_time=sign_time_str, sign=signature)
128+
if canonicalized_param != '':
129+
return rtmp_sign + "&{params}".format(params=canonicalized_param)
130+
else:
131+
return rtmp_sign
95132

96133
if __name__ == "__main__":
97134
pass

qcloud_cos/cos_client.py

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from .streambody import StreamBody
2121
from .xml2dict import Xml2Dict
2222
from .cos_auth import CosS3Auth
23+
from .cos_auth import CosRtmpAuth
2324
from .cos_comm import *
2425
from .cos_threadpool import SimpleThreadPool
2526
from .cos_exception import CosClientError
@@ -3625,6 +3626,327 @@ def get_async_fetch_task(self, Bucket, TaskId, **kwargs):
36253626
data = rt.json()
36263627
return data
36273628

3629+
def put_live_channel(self, Bucket, ChannelName, Expire=3600, LiveChannelConfiguration={}, **kwargs):
3630+
"""创建直播通道
3631+
3632+
:param Bucket(string): 存储桶名称.
3633+
:param ChannelName(string): 直播通道名称.
3634+
:param Expire(int): 推流url签名过期时间.
3635+
:param LiveChannelConfiguration(dict): 直播通道配置.
3636+
:param kwargs(dict): 设置请求headers.
3637+
:return(dict): publish url and playurl.
3638+
3639+
.. code-block:: python
3640+
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
3641+
client = CosS3Client(config)
3642+
# 设置直播通道配置
3643+
livechannel_config = {
3644+
'Description': 'channel description',
3645+
'Switch': 'Enabled',
3646+
'Target': {
3647+
'Type': 'HLS',
3648+
'FragDuration': '3',
3649+
'FragCount': '5',
3650+
}
3651+
}
3652+
response = client.put_live_channel(Bucket='bucket', ChannelName='ch1', LiveChannelConfiguration=livechannel_config)
3653+
"""
3654+
xml_config = format_xml(data=LiveChannelConfiguration, root='LiveChannelConfiguration')
3655+
headers = mapped(kwargs)
3656+
headers['Content-MD5'] = get_md5(xml_config)
3657+
headers['Content-Type'] = 'application/xml'
3658+
params = {'live': ''}
3659+
url = self._conf.uri(bucket=Bucket, path=ChannelName)
3660+
logger.info("put live channel, url=:{url} ,headers=:{headers}".format(url=url, headers=headers))
3661+
rt = self.send_request(
3662+
method='PUT',
3663+
url=url,
3664+
bucket=Bucket,
3665+
data=xml_config,
3666+
auth=CosS3Auth(self._conf, params=params, key=ChannelName),
3667+
headers=headers,
3668+
params=params)
3669+
data = xml_to_dict(rt.content)
3670+
if data['PublishUrls']['Url'] is not None:
3671+
rtmpSign = CosRtmpAuth(self._conf, bucket=Bucket, channel=ChannelName, expire=Expire)
3672+
url = data['PublishUrls']['Url']
3673+
url += '?' + rtmpSign.get_rtmp_sign()
3674+
data['PublishUrls']['Url'] = url
3675+
return data
3676+
3677+
def get_rtmp_signed_url(self, Bucket, ChannelName, Expire=3600, Params={}):
3678+
"""获取直播通道带签名的推流url
3679+
:param Bucket(string): 存储桶名称.
3680+
:param ChannelName(string): 直播通道名称.
3681+
:return: dict.
3682+
3683+
.. code-block:: python
3684+
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
3685+
client = CosS3Client(config)
3686+
resp = client.get_rtmp_signed_url(Bucket='bucket', ChannelName='ch1')
3687+
"""
3688+
rtmp_signed_url = 'rtmp://{bucket}.cos.{region}.myqcloud.com/live/{channel}'.format(bucket=Bucket, region=self._conf._region, channel=ChannelName)
3689+
rtmpAuth = CosRtmpAuth(self._conf, bucket=Bucket, channel=ChannelName, params = Params, expire=Expire)
3690+
return rtmp_signed_url + '?' + rtmpAuth.get_rtmp_sign()
3691+
3692+
def get_live_channel_info(self, Bucket, ChannelName, **kwargs):
3693+
"""获取直播通道配置信息
3694+
3695+
:param Bucket(string): 存储桶名称.
3696+
:param ChannelName(string): 直播通道名称.
3697+
:param kwargs(dict): 设置请求headers.
3698+
:return: dict.
3699+
3700+
.. code-block:: python
3701+
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
3702+
client = CosS3Client(config)
3703+
resp = client.get_live_channel_info(Bucket='bucket', ChannelName='ch1')
3704+
"""
3705+
params = {'live': ''}
3706+
headers = mapped(kwargs)
3707+
url = self._conf.uri(bucket=Bucket, path=ChannelName)
3708+
logger.info("get live channel info, url=:{url} ,headers=:{headers}".format(url=url, headers=headers))
3709+
rt = self.send_request(
3710+
method='GET',
3711+
url=url,
3712+
bucket=Bucket,
3713+
auth=CosS3Auth(self._conf, params=params, key=ChannelName),
3714+
headers=headers,
3715+
params=params)
3716+
data = xml_to_dict(rt.content)
3717+
return data
3718+
3719+
def put_live_channel_switch(self, Bucket, ChannelName, Switch, **kwargs):
3720+
"""禁用或者开启直播通道
3721+
3722+
:param Bucket(string): 存储桶名称.
3723+
:param ChannelName(string): 直播通道名称.
3724+
:param Switch(string): 'enabled'或'disabled'.
3725+
:param kwargs(dict): 设置请求headers.
3726+
:return(None).
3727+
3728+
.. code-block:: python
3729+
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
3730+
client = CosS3Client(config)
3731+
client.put_live_channel_switch(Bucket='bucket', ChannelName='ch1', Switch='enabled')
3732+
"""
3733+
params = {'live': ''}
3734+
if Switch in ['enabled', 'disabled']:
3735+
params['switch'] = Switch
3736+
else:
3737+
raise CosClientError('switch must be enabled or disabled')
3738+
3739+
headers = mapped(kwargs)
3740+
url = self._conf.uri(bucket=Bucket, path=ChannelName)
3741+
logger.info("put live channel switch, url=:{url} ,headers=:{headers}".format(url=url, headers=headers))
3742+
self.send_request(
3743+
method='PUT',
3744+
url=url,
3745+
bucket=Bucket,
3746+
auth=CosS3Auth(self._conf, params=params, key=ChannelName),
3747+
headers=headers,
3748+
params=params)
3749+
return None
3750+
3751+
def get_live_channel_history(self, Bucket, ChannelName, **kwargs):
3752+
"""获取直播通道推流历史
3753+
3754+
:param Bucket(string): 存储桶名称.
3755+
:param ChannelName(string): 直播通道名称.
3756+
:param kwargs(dict): 设置请求headers.
3757+
:return(dict).
3758+
3759+
.. code-block:: python
3760+
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
3761+
client = CosS3Client(config)
3762+
resp = client.get_live_channel_history(Bucket='bucket', ChannelName='ch1')
3763+
"""
3764+
params = {'live': '', 'comp' : 'history'}
3765+
headers = mapped(kwargs)
3766+
url = self._conf.uri(bucket=Bucket, path=ChannelName)
3767+
logger.info("get live channel history, url=:{url} ,headers=:{headers}".format(url=url, headers=headers))
3768+
rt = self.send_request(
3769+
method='GET',
3770+
url=url,
3771+
bucket=Bucket,
3772+
auth=CosS3Auth(self._conf, params=params, key=ChannelName),
3773+
headers=headers,
3774+
params=params)
3775+
data = xml_to_dict(rt.content)
3776+
format_dict(data, ['LiveRecord'])
3777+
return data
3778+
3779+
def get_live_channel_status(self, Bucket, ChannelName, **kwargs):
3780+
"""获取直播通道推流状态
3781+
3782+
:param Bucket(string): 存储桶名称.
3783+
:param ChannelName(string): 直播通道名称.
3784+
:param kwargs(dict): 设置请求headers.
3785+
:return(dict).
3786+
3787+
.. code-block:: python
3788+
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
3789+
client = CosS3Client(config)
3790+
resp = client.get_live_channel_status(Bucket='bucket', ChannelName='ch1')
3791+
"""
3792+
params = {'live': '', 'comp' : 'status'}
3793+
headers = mapped(kwargs)
3794+
url = self._conf.uri(bucket=Bucket, path=ChannelName)
3795+
logger.info("get live channel status, url=:{url} ,headers=:{headers}".format(url=url, headers=headers))
3796+
rt = self.send_request(
3797+
method='GET',
3798+
url=url,
3799+
bucket=Bucket,
3800+
auth=CosS3Auth(self._conf, params=params, key=ChannelName),
3801+
headers=headers,
3802+
params=params)
3803+
data = xml_to_dict(rt.content)
3804+
return data
3805+
3806+
def delete_live_channel(self, Bucket, ChannelName, **kwargs):
3807+
"""删除直播通道
3808+
3809+
:param Bucket(string): 存储桶名称.
3810+
:param ChannelName(string): 直播通道名称.
3811+
:param kwargs(dict): 设置请求headers.
3812+
:return(dict).
3813+
3814+
.. code-block:: python
3815+
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
3816+
client = CosS3Client(config)
3817+
client.delete_live_channel(Bucket='bucket', ChannelName='ch1')
3818+
"""
3819+
params = {'live': ''}
3820+
url = self._conf.uri(bucket=Bucket, path=ChannelName)
3821+
headers = mapped(kwargs)
3822+
logger.info("delete live channel, url=:{url} ,headers=:{headers}".format(url=url, headers=headers))
3823+
rt = self.send_request(
3824+
method='DELETE',
3825+
url=url,
3826+
bucket=Bucket,
3827+
auth=CosS3Auth(self._conf, params=params, key=ChannelName),
3828+
headers=headers,
3829+
params=params)
3830+
data = dict(**rt.headers)
3831+
return data
3832+
3833+
def get_vod_playlist(self, Bucket, ChannelName, StartTime = 0, EndTime = 0, **kwargs):
3834+
"""查询指定时间段播放列表文件
3835+
3836+
:param Bucket(string): 存储桶名称.
3837+
:param ChannelName(string): 直播通道名称.
3838+
:param StartTime(int): 播放列表ts文件的起始时间,格式为unix时间戳.
3839+
:param EndTime(int): 播放列表ts文件的结束时间,格式为unix时间戳.
3840+
:param kwargs(dict): 设置请求headers.
3841+
:return(string).
3842+
3843+
.. code-block:: python
3844+
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
3845+
client = CosS3Client(config)
3846+
resp = client.get_vod_playlist(Bucket='bucket', ChannelName='ch1', StartTime=1611218201, EndTime=1611218300)
3847+
"""
3848+
if StartTime <= 0 or EndTime <= 0:
3849+
raise CosClientError('invalid timestamp')
3850+
if StartTime >= EndTime:
3851+
raise CosClientError('StartTime must be less than EndTime')
3852+
3853+
params = {'vod': '', 'starttime' : StartTime, 'endtime' : EndTime}
3854+
headers = mapped(kwargs)
3855+
url = self._conf.uri(bucket=Bucket, path=ChannelName)
3856+
logger.info("get vod playlist, url=:{url} ,headers=:{headers}".format(url=url, headers=headers))
3857+
rt = self.send_request(
3858+
method='GET',
3859+
url=url,
3860+
bucket=Bucket,
3861+
auth=CosS3Auth(self._conf, params=params, key=ChannelName),
3862+
headers=headers,
3863+
params=params)
3864+
return rt.content
3865+
3866+
def post_vod_playlist(self, Bucket, ChannelName, PlaylistName, StartTime = 0, EndTime = 0, **kwargs):
3867+
"""生成点播播放列表文件
3868+
3869+
:param Bucket(string): 存储桶名称.
3870+
:param ChannelName(string): 直播通道名称.
3871+
:param PlaylistName(string): 播放列表文件名称.
3872+
:param StartTime(int): 播放列表ts文件的起始时间,格式为unix时间戳.
3873+
:param EndTime(int): 播放列表ts文件的结束时间,格式为unix时间戳.
3874+
:param kwargs(dict): 设置请求headers.
3875+
:return(None).
3876+
3877+
.. code-block:: python
3878+
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
3879+
client = CosS3Client(config)
3880+
resp = client.post_vod_playlist(Bucket='bucket', ChannelName='ch1', PlaylistName='test.m3u8', StartTime=1611218201, EndTime=1611218300)
3881+
"""
3882+
if StartTime <= 0 or EndTime <= 0:
3883+
raise CosClientError('invalid timestamp')
3884+
if StartTime >= EndTime:
3885+
raise CosClientError('StartTime must be less than EndTime')
3886+
if not PlaylistName.endswith('.m3u8'):
3887+
raise CosClientError('PlaylistName must be end with .m3u8')
3888+
3889+
params = {'vod': '', 'starttime' : StartTime, 'endtime' : EndTime}
3890+
headers = mapped(kwargs)
3891+
file_path = ChannelName + '/' + PlaylistName
3892+
url = self._conf.uri(bucket=Bucket, path=file_path)
3893+
logger.info("post vod playlist, url=:{url} ,headers=:{headers}".format(url=url, headers=headers))
3894+
rt = self.send_request(
3895+
method='POST',
3896+
url=url,
3897+
bucket=Bucket,
3898+
auth=CosS3Auth(self._conf, params=params, key=file_path),
3899+
headers=headers,
3900+
params=params)
3901+
return None
3902+
3903+
def list_live_channel(self, Bucket, MaxKeys = 100, Prefix = '', Marker = '', **kwargs):
3904+
"""获取直播通道列表
3905+
3906+
:param Bucket(string): 存储桶名称.
3907+
:param MaxKeys(int): 每页可以列出通道数量的最大值,有效值范围为[1, 1000],默认值:100.
3908+
:param Prefix(string): 限定返回的 LiveChannel 必须以 prefix 作为前缀.
3909+
:param Marker(string): 从 marker 之后按字母排序的第一个开始返回.
3910+
:param kwargs(dict): 设置请求headers.
3911+
:return: string.
3912+
3913+
.. code-block:: python
3914+
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
3915+
client = CosS3Client(config)
3916+
resp = client.list_channel(Bucket='bucket', MaxKeys=100)
3917+
"""
3918+
params = {'live' : ''}
3919+
if MaxKeys >= 1:
3920+
params['max-keys'] = MaxKeys
3921+
if Prefix != '':
3922+
params['prefix'] = Prefix
3923+
if Marker != '':
3924+
params['marker'] = Marker
3925+
headers = mapped(kwargs)
3926+
url = self._conf.uri(bucket=Bucket)
3927+
logger.info("list live channel, url=:{url} ,headers=:{headers}".format(url=url, headers=headers))
3928+
rt = self.send_request(
3929+
method='GET',
3930+
url=url,
3931+
bucket=Bucket,
3932+
auth=CosS3Auth(self._conf, params=params),
3933+
headers=headers,
3934+
params=params)
3935+
data = xml_to_dict(rt.content)
3936+
format_dict(data, ['LiveChannel'])
3937+
decode_result(
3938+
data,
3939+
[
3940+
'Prefix',
3941+
'Marker',
3942+
'MaxKeys',
3943+
'IsTruncated',
3944+
'NextMarker'
3945+
],
3946+
[
3947+
['LiveChannel', 'Name'],
3948+
])
3949+
return data
36283950

36293951
if __name__ == "__main__":
36303952
pass

0 commit comments

Comments
 (0)