From 535febacc8d5865e7beaea5eca0eb4e9a38219bd Mon Sep 17 00:00:00 2001 From: jackyding Date: Sat, 23 Jan 2021 12:18:31 +0800 Subject: [PATCH 1/5] add cos live --- qcloud_cos/cos_auth.py | 27 ++++ qcloud_cos/cos_client.py | 308 +++++++++++++++++++++++++++++++++++++++ ut/test.py | 115 +++++++++++++++ 3 files changed, 450 insertions(+) diff --git a/qcloud_cos/cos_auth.py b/qcloud_cos/cos_auth.py index 20287602..fce835ee 100644 --- a/qcloud_cos/cos_auth.py +++ b/qcloud_cos/cos_auth.py @@ -83,6 +83,33 @@ def __call__(self, r): logger.debug("request headers: " + str(r.headers)) return r +class CosRtmpAuth(AuthBase): + def __init__(self, conf, bucket=None, channel=None, params={}, expire=10000): + self._secret_id = conf._secret_id + self._secret_key = conf._secret_key + self._anonymous = conf._anonymous + self._expire = expire + self._params = params + self._path = u'/' + bucket + u'/' + channel + + def get_rtmp_sign(self): + # make params empty for now + # get rtmp string + rtmp_str = u"{path}\n{params}\n".format(path=self._path, params='') + logger.debug("rtmp str: " + rtmp_str) + + sha1 = hashlib.sha1() + sha1.update(to_bytes(rtmp_str)) + # get time + sign_time = int(time.time()) + sign_time_str = "{start_time};{end_time}".format(start_time = sign_time - 60, end_time = sign_time + self._expire) + str_to_sign = "sha1\n{time}\n{sha1}\n".format(time = sign_time_str, sha1 = sha1.hexdigest()) + logger.debug('str_to_sign: ' + str(str_to_sign)) + # get sinature + signature = hmac.new(to_bytes(self._secret_key), to_bytes(str_to_sign), hashlib.sha1).hexdigest() + logger.debug('signature: ' + str(signature)) + sign_tpl = "q-sign-algorithm=sha1&q-ak={ak}&q-sign-time={sign_time}&q-key-time={key_time}&q-signature={sign}" + return sign_tpl.format(ak=self._secret_id, sign_time=sign_time_str, key_time=sign_time_str, sign=signature) if __name__ == "__main__": pass diff --git a/qcloud_cos/cos_client.py b/qcloud_cos/cos_client.py index d9f6ebf4..070615ef 100644 --- a/qcloud_cos/cos_client.py +++ b/qcloud_cos/cos_client.py @@ -19,6 +19,7 @@ from .streambody import StreamBody from .xml2dict import Xml2Dict from .cos_auth import CosS3Auth +from .cos_auth import CosRtmpAuth from .cos_comm import * from .cos_threadpool import SimpleThreadPool from .cos_exception import CosClientError @@ -3477,6 +3478,313 @@ def get_async_fetch_task(self, Bucket, TaskId, **kwargs): data = rt.json() return data + def put_live_channel(self, Bucket, ChannelName, Expire=3600, LiveChannelConfiguration={}, **kwargs): + """创建直播通道 + + :param Bucket(string): 存储桶名称. + :param ChannelName(string): 直播通道名称. + :param Expire(int): 推流url签名过期时间. + :param LiveChannelConfiguration(dict): 直播通道配置. + :param kwargs(dict): 设置请求headers. + :return(dict): publish url and playurl. + + .. code-block:: python + config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象 + client = CosS3Client(config) + # 设置直播通道配置 + livechannel_config = { + 'Description': 'channel description', + 'Switch': 'Enabled', + 'Target': { + 'Type': 'HLS', + 'FragDuration': '3', + 'FragCount': '5', + } + } + response = client.put_live_channel(Bucket='bucket', ChannelName='ch1', LiveChannelConfiguration=livechannel_config) + """ + xml_config = format_xml(data=LiveChannelConfiguration, root='LiveChannelConfiguration') + headers = mapped(kwargs) + headers['Content-MD5'] = get_md5(xml_config) + headers['Content-Type'] = 'application/xml' + params = {'live': ''} + url = self._conf.uri(bucket=Bucket, path=ChannelName) + logger.info("put live channel, url=:{url} ,headers=:{headers}".format(url=url, headers=headers)) + rt = self.send_request( + method='PUT', + url=url, + bucket=Bucket, + data=xml_config, + auth=CosS3Auth(self._conf, params=params, key=ChannelName), + headers=headers, + params=params) + data = xml_to_dict(rt.content) + if data['PublishUrls']['Url'] is not None: + rtmpSign = CosRtmpAuth(self._conf, bucket=Bucket, channel=ChannelName, expire=Expire) + url = data['PublishUrls']['Url'] + url += '?' + rtmpSign.get_rtmp_sign() + data['PublishUrls']['Url'] = url + return data + + def get_live_channel_info(self, Bucket, ChannelName, **kwargs): + """获取直播通道配置信息 + + :param Bucket(string): 存储桶名称. + :param ChannelName(string): 直播通道名称. + :param kwargs(dict): 设置请求headers. + :return: dict. + + .. code-block:: python + config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象 + client = CosS3Client(config) + resp = client.get_live_channel_info(Bucket='bucket', ChannelName='ch1') + """ + params = {'live': ''} + headers = mapped(kwargs) + url = self._conf.uri(bucket=Bucket, path=ChannelName) + logger.info("get live channel info, url=:{url} ,headers=:{headers}".format(url=url, headers=headers)) + rt = self.send_request( + method='GET', + url=url, + bucket=Bucket, + auth=CosS3Auth(self._conf, params=params, key=ChannelName), + headers=headers, + params=params) + data = xml_to_dict(rt.content) + return data + + def put_live_channel_switch(self, Bucket, ChannelName, Switch, **kwargs): + """禁用或者开启直播通道 + + :param Bucket(string): 存储桶名称. + :param ChannelName(string): 直播通道名称. + :param Switch(string): 'enabled'或'disabled'. + :param kwargs(dict): 设置请求headers. + :return(None). + + .. code-block:: python + config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象 + client = CosS3Client(config) + client.put_live_channel_switch(Bucket='bucket', ChannelName='ch1', Switch='enabled') + """ + params = {'live': ''} + if Switch in ['enabled', 'disabled']: + params['switch'] = Switch + else: + raise CosClientError('switch must be enabled or disabled') + + headers = mapped(kwargs) + url = self._conf.uri(bucket=Bucket, path=ChannelName) + logger.info("put live channel switch, url=:{url} ,headers=:{headers}".format(url=url, headers=headers)) + self.send_request( + method='PUT', + url=url, + bucket=Bucket, + auth=CosS3Auth(self._conf, params=params, key=ChannelName), + headers=headers, + params=params) + return None + + def get_live_channel_history(self, Bucket, ChannelName, **kwargs): + """获取直播通道推流历史 + + :param Bucket(string): 存储桶名称. + :param ChannelName(string): 直播通道名称. + :param kwargs(dict): 设置请求headers. + :return(dict). + + .. code-block:: python + config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象 + client = CosS3Client(config) + resp = client.get_live_channel_history(Bucket='bucket', ChannelName='ch1') + """ + params = {'live': '', 'comp' : 'history'} + headers = mapped(kwargs) + url = self._conf.uri(bucket=Bucket, path=ChannelName) + logger.info("get live channel history, url=:{url} ,headers=:{headers}".format(url=url, headers=headers)) + rt = self.send_request( + method='GET', + url=url, + bucket=Bucket, + auth=CosS3Auth(self._conf, params=params, key=ChannelName), + headers=headers, + params=params) + data = xml_to_dict(rt.content) + format_dict(data, ['LiveRecord']) + return data + + def get_live_channel_status(self, Bucket, ChannelName, **kwargs): + """获取直播通道推流状态 + + :param Bucket(string): 存储桶名称. + :param ChannelName(string): 直播通道名称. + :param kwargs(dict): 设置请求headers. + :return(dict). + + .. code-block:: python + config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象 + client = CosS3Client(config) + resp = client.get_live_channel_status(Bucket='bucket', ChannelName='ch1') + """ + params = {'live': '', 'comp' : 'status'} + headers = mapped(kwargs) + url = self._conf.uri(bucket=Bucket, path=ChannelName) + logger.info("get live channel status, url=:{url} ,headers=:{headers}".format(url=url, headers=headers)) + rt = self.send_request( + method='GET', + url=url, + bucket=Bucket, + auth=CosS3Auth(self._conf, params=params, key=ChannelName), + headers=headers, + params=params) + data = xml_to_dict(rt.content) + return data + + def delete_live_channel(self, Bucket, ChannelName, **kwargs): + """删除直播通道 + + :param Bucket(string): 存储桶名称. + :param ChannelName(string): 直播通道名称. + :param kwargs(dict): 设置请求headers. + :return(dict). + + .. code-block:: python + config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象 + client = CosS3Client(config) + client.delete_live_channel(Bucket='bucket', ChannelName='ch1') + """ + params = {'live': ''} + url = self._conf.uri(bucket=Bucket, path=ChannelName) + headers = mapped(kwargs) + logger.info("delete live channel, url=:{url} ,headers=:{headers}".format(url=url, headers=headers)) + rt = self.send_request( + method='DELETE', + url=url, + bucket=Bucket, + auth=CosS3Auth(self._conf, params=params, key=ChannelName), + headers=headers, + params=params) + data = dict(**rt.headers) + return data + + def get_vod_playlist(self, Bucket, ChannelName, StartTime = 0, EndTime = 0, **kwargs): + """查询指定时间段播放列表文件 + + :param Bucket(string): 存储桶名称. + :param ChannelName(string): 直播通道名称. + :param StartTime(int): 播放列表ts文件的起始时间,格式为unix时间戳. + :param EndTime(int): 播放列表ts文件的结束时间,格式为unix时间戳. + :param kwargs(dict): 设置请求headers. + :return(string). + + .. code-block:: python + config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象 + client = CosS3Client(config) + resp = client.get_vod_playlist(Bucket='bucket', ChannelName='ch1', StartTime=1611218201, EndTime=1611218300) + """ + if StartTime <= 0 or EndTime <= 0: + raise CosClientError('invalid timestamp') + if StartTime >= EndTime: + raise CosClientError('StartTime must be less than EndTime') + + params = {'vod': '', 'starttime' : StartTime, 'endtime' : EndTime} + headers = mapped(kwargs) + url = self._conf.uri(bucket=Bucket, path=ChannelName) + logger.info("get vod playlist, url=:{url} ,headers=:{headers}".format(url=url, headers=headers)) + rt = self.send_request( + method='GET', + url=url, + bucket=Bucket, + auth=CosS3Auth(self._conf, params=params, key=ChannelName), + headers=headers, + params=params) + return rt.content + + def post_vod_playlist(self, Bucket, ChannelName, PlaylistName, StartTime = 0, EndTime = 0, **kwargs): + """生成点播播放列表文件 + + :param Bucket(string): 存储桶名称. + :param ChannelName(string): 直播通道名称. + :param PlaylistName(string): 播放列表文件名称. + :param StartTime(int): 播放列表ts文件的起始时间,格式为unix时间戳. + :param EndTime(int): 播放列表ts文件的结束时间,格式为unix时间戳. + :param kwargs(dict): 设置请求headers. + :return(None). + + .. code-block:: python + config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象 + client = CosS3Client(config) + resp = client.post_vod_playlist(Bucket='bucket', ChannelName='ch1', PlaylistName='test.m3u8', StartTime=1611218201, EndTime=1611218300) + """ + if StartTime <= 0 or EndTime <= 0: + raise CosClientError('invalid timestamp') + if StartTime >= EndTime: + raise CosClientError('StartTime must be less than EndTime') + if not PlaylistName.endswith('.m3u8'): + raise CosClientError('PlaylistName must be end with .m3u8') + + params = {'vod': '', 'starttime' : StartTime, 'endtime' : EndTime} + headers = mapped(kwargs) + file_path = ChannelName + '/' + PlaylistName + url = self._conf.uri(bucket=Bucket, path=file_path) + logger.info("post vod playlist, url=:{url} ,headers=:{headers}".format(url=url, headers=headers)) + rt = self.send_request( + method='POST', + url=url, + bucket=Bucket, + auth=CosS3Auth(self._conf, params=params, key=file_path), + headers=headers, + params=params) + return None + + '''def list_live_channel(self, Bucket, MaxKeys = 100, Prefix = '', Marker = '', **kwargs): + """获取直播通道列表 + + :param Bucket(string): 存储桶名称. + :param MaxKeys(int): 每页可以列出通道数量的最大值,有效值范围为[1, 1000],默认值:100. + :param Prefix(string): 限定返回的 LiveChannel 必须以 prefix 作为前缀. + :param Marker(string): 从 marker 之后按字母排序的第一个开始返回. + :param kwargs(dict): 设置请求headers. + :return: string. + + .. code-block:: python + config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象 + client = CosS3Client(config) + resp = client.list_channel(Bucket='bucket', MaxKeys=100) + """ + params = {'live' : ''} + if MaxKeys >= 1: + params['max-keys'] = MaxKeys + if Prefix != '': + params['prefix'] = Prefix + if Marker != '': + params['marker'] = Marker + headers = mapped(kwargs) + url = self._conf.uri(bucket=Bucket) + logger.info("list live channel, url=:{url} ,headers=:{headers}".format(url=url, headers=headers)) + rt = self.send_request( + method='GET', + url=url, + bucket=Bucket, + auth=CosS3Auth(self._conf, params=params), + headers=headers, + params=params) + data = xml_to_dict(rt.content) + format_dict(data, ['LiveChannel']) + decode_result( + data, + [ + 'Prefix', + 'Marker', + 'MaxKeys', + 'IsTruncated', + 'NextMarker' + ], + [ + ['LiveChannel', 'Name'], + ]) + return data +''' if __name__ == "__main__": pass diff --git a/ut/test.py b/ut/test.py index 2de93b58..318284c5 100644 --- a/ut/test.py +++ b/ut/test.py @@ -1165,6 +1165,120 @@ def _test_get_object_sensitive_content_recognition(): assert response +def test_live_channel(): + print ("create live channel...") + livechannel_config = { + 'Description': 'cos python sdk test', + 'Switch': 'Enabled', + 'Target': { + 'Type': 'HLS', + 'FragDuration': '3', + 'FragCount': '5', + } + } + channel_name = 'cos-python-sdk-uttest-ch1' + try: + response = client.put_live_channel( + Bucket = test_bucket, + ChannelName = channel_name, + LiveChannelConfiguration = livechannel_config) + assert(response) + print(response) + except Exception as e: + if e.get_error_code() != 'ChannelStillLive': + return + + print ("get live channel info...") + response = client.get_live_channel_info( + Bucket = test_bucket, + ChannelName = channel_name) + print(response) + assert(response['Switch'] == 'Enabled') + assert(response['Description'] == 'cos python sdk test') + assert(response['Target']['Type'] == 'HLS') + assert(response['Target']['FragDuration'] == '3') + assert(response['Target']['FragCount'] == '5') + assert(response['Target']['PlaylistName'] == 'playlist.m3u8') + + print ("put live channel switch...") + client.put_live_channel_switch( + Bucket = test_bucket, + ChannelName = channel_name, + Switch = 'disabled') + response = client.get_live_channel_info( + Bucket=test_bucket, + ChannelName=channel_name) + assert(response['Switch'] == 'Disabled') + client.put_live_channel_switch( + Bucket=test_bucket, + ChannelName=channel_name, + Switch='enabled') + response = client.get_live_channel_info( + Bucket=test_bucket, + ChannelName=channel_name) + assert (response['Switch'] == 'Enabled') + + print ("get live channel history...") + response = client.get_live_channel_history( + Bucket = test_bucket, + ChannelName = channel_name) + print(response) + + print ("get live channel status...") + response = client.get_live_channel_status( + Bucket = test_bucket, + ChannelName = channel_name) + print(response) + assert (response['Status'] == 'Idle' or response['Status'] == 'Live') + + print ("list channel...") + #for i in range(1, 20): + # channel_name = 'test-list-channel-' + str(i) + # client.put_live_channel( + # Bucket=test_bucket, + # ChannelName=channel_name, + # LiveChannelConfiguration=livechannel_config) + #response = client.list_live_channel(Bucket = test_bucket, MaxKeys = 10) + #print(response) + #for i in range(1, 100): + # channel_name = 'test-list-channel-' + str(i) + # client.delete_live_channel(Bucket=test_bucket, ChannelName=channel_name) + + print ("post vod playlist") + try: + client.post_vod_playlist( + Bucket = test_bucket, + ChannelName = channel_name, + PlaylistName = 'test', + StartTime = int(time.time()) - 10000, + EndTime = int(time.time())) + except Exception as e: + print e + try: + client.post_vod_playlist( + Bucket = test_bucket, + ChannelName = channel_name, + PlaylistName = 'test.m3u8', + StartTime = 10, + EndTime = 9) + except Exception as e: + print e + + client.post_vod_playlist( + Bucket = test_bucket, + ChannelName = channel_name, + PlaylistName = 'test.m3u8', + StartTime = int(time.time()) - 10000, + EndTime = int(time.time())) + response = client.head_object( + Bucket = test_bucket, + Key = channel_name + '/test.m3u8') + assert(response) + + print ("delete live channel...") + response = client.delete_live_channel(Bucket=test_bucket, ChannelName=channel_name) + assert(response) + if __name__ == "__main__": setUp() """ @@ -1190,6 +1304,7 @@ def _test_get_object_sensitive_content_recognition(): test_put_get_delete_bucket_domain() test_select_object() _test_get_object_sensitive_content_recognition() + test_live_channel() """ tearDown() From f6fe37d74da27646b3cd8b537892dba285be291f Mon Sep 17 00:00:00 2001 From: jackyding Date: Thu, 28 Jan 2021 19:15:46 +0800 Subject: [PATCH 2/5] fix list bug --- qcloud_cos/cos_client.py | 3 +-- ut/test.py | 35 ++++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/qcloud_cos/cos_client.py b/qcloud_cos/cos_client.py index 070615ef..1e578454 100644 --- a/qcloud_cos/cos_client.py +++ b/qcloud_cos/cos_client.py @@ -3737,7 +3737,7 @@ def post_vod_playlist(self, Bucket, ChannelName, PlaylistName, StartTime = 0, En params=params) return None - '''def list_live_channel(self, Bucket, MaxKeys = 100, Prefix = '', Marker = '', **kwargs): + def list_live_channel(self, Bucket, MaxKeys = 100, Prefix = '', Marker = '', **kwargs): """获取直播通道列表 :param Bucket(string): 存储桶名称. @@ -3784,7 +3784,6 @@ def post_vod_playlist(self, Bucket, ChannelName, PlaylistName, StartTime = 0, En ['LiveChannel', 'Name'], ]) return data -''' if __name__ == "__main__": pass diff --git a/ut/test.py b/ut/test.py index 318284c5..2293320f 100644 --- a/ut/test.py +++ b/ut/test.py @@ -17,7 +17,7 @@ TRAVIS_FLAG = os.environ["TRAVIS_FLAG"] REGION = os.environ["REGION"] APPID = '1251668577' -test_bucket = 'cos-python-v5-test-' + str(sys.version_info[0]) + '-' + str(sys.version_info[1]) + '-' + REGION + '-' + APPID +test_bucket = 'jackytestgz1-' + APPID copy_test_bucket = 'copy-' + test_bucket test_object = "test.txt" special_file_name = "中文" + "→↓←→↖↗↙↘! \"#$%&'()*+,-./0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" @@ -1177,6 +1177,7 @@ def test_live_channel(): } } channel_name = 'cos-python-sdk-uttest-ch1' + try: response = client.put_live_channel( Bucket = test_bucket, @@ -1232,17 +1233,25 @@ def test_live_channel(): assert (response['Status'] == 'Idle' or response['Status'] == 'Live') print ("list channel...") - #for i in range(1, 20): - # channel_name = 'test-list-channel-' + str(i) - # client.put_live_channel( - # Bucket=test_bucket, - # ChannelName=channel_name, - # LiveChannelConfiguration=livechannel_config) - #response = client.list_live_channel(Bucket = test_bucket, MaxKeys = 10) - #print(response) - #for i in range(1, 100): - # channel_name = 'test-list-channel-' + str(i) - # client.delete_live_channel(Bucket=test_bucket, ChannelName=channel_name) + create_chan_num = 20 + for i in range(1, create_chan_num): + ch_name = 'test-list-channel-' + str(i) + client.put_live_channel( + Bucket=test_bucket, + ChannelName=ch_name, + LiveChannelConfiguration=livechannel_config) + response = client.list_live_channel(Bucket = test_bucket, MaxKeys = 10) + print(response) + assert (response['MaxKeys'] == '10') + assert (response['IsTruncated'] == 'true') + response = client.list_live_channel(Bucket=test_bucket, MaxKeys=5, Marker = response['NextMarker']) + print(response) + assert (response['MaxKeys'] == '5') + assert (response['IsTruncated'] == 'true') + + for i in range(1, create_chan_num): + ch_name = 'test-list-channel-' + str(i) + client.delete_live_channel(Bucket=test_bucket, ChannelName=ch_name) print ("post vod playlist") try: @@ -1304,7 +1313,7 @@ def test_live_channel(): test_put_get_delete_bucket_domain() test_select_object() _test_get_object_sensitive_content_recognition() - test_live_channel() """ + test_live_channel() tearDown() From e61e4265b234fef738451b537d3e40a9b61c087c Mon Sep 17 00:00:00 2001 From: jackyding Date: Thu, 28 Jan 2021 19:18:51 +0800 Subject: [PATCH 3/5] modify ut --- ut/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ut/test.py b/ut/test.py index 2293320f..f4943485 100644 --- a/ut/test.py +++ b/ut/test.py @@ -17,7 +17,7 @@ TRAVIS_FLAG = os.environ["TRAVIS_FLAG"] REGION = os.environ["REGION"] APPID = '1251668577' -test_bucket = 'jackytestgz1-' + APPID +test_bucket = 'cos-python-v5-test-' + str(sys.version_info[0]) + '-' + str(sys.version_info[1]) + '-' + REGION + '-' + APPID copy_test_bucket = 'copy-' + test_bucket test_object = "test.txt" special_file_name = "中文" + "→↓←→↖↗↙↘! \"#$%&'()*+,-./0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" From 3be00180571aa4b9569ff337737526d7afd174bd Mon Sep 17 00:00:00 2001 From: jackyding Date: Thu, 28 Jan 2021 19:19:56 +0800 Subject: [PATCH 4/5] modify ut --- ut/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ut/test.py b/ut/test.py index f4943485..37bbd5b6 100644 --- a/ut/test.py +++ b/ut/test.py @@ -1313,7 +1313,7 @@ def test_live_channel(): test_put_get_delete_bucket_domain() test_select_object() _test_get_object_sensitive_content_recognition() - """ test_live_channel() + """ tearDown() From fbb946fbe8f2047bc776a82ec741253831d9c146 Mon Sep 17 00:00:00 2001 From: jackyding Date: Wed, 3 Feb 2021 11:28:58 +0800 Subject: [PATCH 5/5] support token --- qcloud_cos/cos_auth.py | 18 ++++++++++++++---- qcloud_cos/cos_client.py | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/qcloud_cos/cos_auth.py b/qcloud_cos/cos_auth.py index fce835ee..a77f0d2c 100644 --- a/qcloud_cos/cos_auth.py +++ b/qcloud_cos/cos_auth.py @@ -87,15 +87,21 @@ class CosRtmpAuth(AuthBase): def __init__(self, conf, bucket=None, channel=None, params={}, expire=10000): self._secret_id = conf._secret_id self._secret_key = conf._secret_key + self._token = conf._token self._anonymous = conf._anonymous self._expire = expire self._params = params + if self._token: + self._params['q-token'] = self._token self._path = u'/' + bucket + u'/' + channel def get_rtmp_sign(self): - # make params empty for now # get rtmp string - rtmp_str = u"{path}\n{params}\n".format(path=self._path, params='') + canonicalized_param = '' + for k, v in self._params.iteritems(): + canonicalized_param += '{key}={value}&'.format(key=k, value=v) + canonicalized_param = canonicalized_param.rstrip('&') + rtmp_str = u"{path}\n{params}\n".format(path=self._path, params=canonicalized_param) logger.debug("rtmp str: " + rtmp_str) sha1 = hashlib.sha1() @@ -108,8 +114,12 @@ def get_rtmp_sign(self): # get sinature signature = hmac.new(to_bytes(self._secret_key), to_bytes(str_to_sign), hashlib.sha1).hexdigest() logger.debug('signature: ' + str(signature)) - sign_tpl = "q-sign-algorithm=sha1&q-ak={ak}&q-sign-time={sign_time}&q-key-time={key_time}&q-signature={sign}" - return sign_tpl.format(ak=self._secret_id, sign_time=sign_time_str, key_time=sign_time_str, sign=signature) + rtmp_sign = "q-sign-algorithm=sha1&q-ak={ak}&q-sign-time={sign_time}&q-key-time={key_time}&q-signature={sign}".format( + ak=self._secret_id, sign_time=sign_time_str, key_time=sign_time_str, sign=signature) + if canonicalized_param != '': + return rtmp_sign + "&{params}".format(params=canonicalized_param) + else: + return rtmp_sign if __name__ == "__main__": pass diff --git a/qcloud_cos/cos_client.py b/qcloud_cos/cos_client.py index 1e578454..bbcd3596 100644 --- a/qcloud_cos/cos_client.py +++ b/qcloud_cos/cos_client.py @@ -3526,6 +3526,21 @@ def put_live_channel(self, Bucket, ChannelName, Expire=3600, LiveChannelConfigur data['PublishUrls']['Url'] = url return data + def get_rtmp_signed_url(self, Bucket, ChannelName, Expire=3600, Params={}): + """获取直播通道带签名的推流url + :param Bucket(string): 存储桶名称. + :param ChannelName(string): 直播通道名称. + :return: dict. + + .. code-block:: python + config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象 + client = CosS3Client(config) + resp = client.get_rtmp_signed_url(Bucket='bucket', ChannelName='ch1') + """ + rtmp_signed_url = 'rtmp://{bucket}.cos.{region}.myqcloud.com/live/{channel}'.format(bucket=Bucket, region=self._conf._region, channel=ChannelName) + rtmpAuth = CosRtmpAuth(self._conf, bucket=Bucket, channel=ChannelName, params = Params, expire=Expire) + return rtmp_signed_url + '?' + rtmpAuth.get_rtmp_sign() + def get_live_channel_info(self, Bucket, ChannelName, **kwargs): """获取直播通道配置信息