|
- # -*- coding: UTF-8 -*-
- import os, sys
- import hashlib
- import datetime
- import functools
- import logging
- from loguru import logger
- from oss2.exceptions import OssError
- from aliyunsdkcore.acs_exception.exceptions import ServerException
- from aliyunsdkcore.acs_exception.exceptions import ClientException
- import traceback
- import requests
-
- if sys.version_info[0] == 3:
- import urllib.parse
- else:
- from urllib import unquote
-
- VOD_PRINT_INFO_LOG_SWITCH = 1
-
-
- class AliyunVodLog:
- """
- VOD日志类,基于logging实现
- """
-
- @staticmethod
- def printLogStr(msg, *args, **kwargs):
- if VOD_PRINT_INFO_LOG_SWITCH:
- print("[%s]%s" % (AliyunVodUtils.getCurrentTimeStr(), msg))
-
- @staticmethod
- def info(msg, *args, **kwargs):
- logging.info(msg, *args, **kwargs)
- AliyunVodLog.printLogStr(msg, *args, **kwargs)
-
- @staticmethod
- def error(msg, *args, **kwargs):
- logging.error(msg, *args, **kwargs)
- AliyunVodLog.printLogStr(msg, *args, **kwargs)
-
- @staticmethod
- def warning(msg, *args, **kwargs):
- logging.warning(msg, *args, **kwargs)
- AliyunVodLog.printLogStr(msg, *args, **kwargs)
-
-
- class AliyunVodUtils:
- """
- VOD上传SDK的工具类,提供截取字符串、获取扩展名、获取文件名等静态函数
- """
-
- # 截取字符串,在不超过最大字节数前提下确保中文字符不被截断出现乱码(先转换成unicode,再取子串,然后转换成utf-8)
- @staticmethod
- def subString(strVal, maxBytes, charSet='utf-8'):
- i = maxBytes
- if sys.version_info[0] == 3:
- while len(strVal.encode(charSet)) > maxBytes:
- if i < 0:
- return ''
- strVal = strVal[:i]
- i -= 1
- else:
- while len(strVal) > maxBytes:
- if i < 0:
- return ''
- strVal = strVal.decode(charSet)[:i].encode(charSet)
- i -= 1
- return strVal
-
- @staticmethod
- def getFileExtension(fileName):
- end = fileName.rfind('?')
- if end <= 0:
- end = len(fileName)
-
- i = fileName.rfind('.')
- if i >= 0:
- return fileName[i + 1:end].lower()
- else:
- return None
-
- # urldecode
- @staticmethod
- def urlDecode(fileUrl):
- if sys.version_info[0] == 3:
- return urllib.parse.unquote(fileUrl)
- else:
- return unquote(fileUrl)
-
- # urlencode
- @staticmethod
- def urlEncode(fileUrl):
- if sys.version_info[0] == 3:
- return urllib.parse.urlencode(fileUrl)
- else:
- return urllib.urlencode(fileUrl)
-
- # 获取Url的摘要地址(去除?后的参数,如果有)以及文件名
- @staticmethod
- def getFileBriefPath(fileUrl):
- # fileUrl = AliyunVodUtils.urlDecode(fileUrl)
- i = fileUrl.rfind('?')
- if i > 0:
- briefPath = fileUrl[:i]
- else:
- briefPath = fileUrl
-
- briefName = os.path.basename(briefPath)
- return briefPath, AliyunVodUtils.urlDecode(briefName)
-
- @staticmethod
- def getStringMd5(strVal, isEncode=True):
- m = hashlib.md5()
- m.update(strVal.encode('utf-8') if isEncode else strVal)
- return m.hexdigest()
-
- @staticmethod
- def getCurrentTimeStr():
- now = datetime.datetime.now()
- return now.strftime("%Y-%m-%d %H:%M:%S")
-
- # 将oss地址转换为内网地址(如果脚本部署的ecs与oss bucket在同一区域)
- @staticmethod
- def convertOssInternal(ossUrl, ecsRegion=None, isVpc=False):
- if (not ossUrl) or (not ecsRegion):
- return ossUrl
-
- availableRegions = ['cn-qingdao', 'cn-beijing', 'cn-zhangjiakou', 'cn-huhehaote', 'cn-hangzhou', 'cn-shanghai',
- 'cn-shenzhen',
- 'cn-hongkong', 'ap-southeast-1', 'ap-southeast-2', 'ap-southeast-3',
- 'ap-northeast-1', 'us-west-1', 'us-east-1', 'eu-central-1', 'me-east-1']
- if ecsRegion not in availableRegions:
- return ossUrl
-
- ossUrl = ossUrl.replace("https:", "http:")
- if isVpc:
- return ossUrl.replace("oss-%s.aliyuncs.com" % (ecsRegion), "vpc100-oss-%s.aliyuncs.com" % (ecsRegion))
- else:
- return ossUrl.replace("oss-%s.aliyuncs.com" % (ecsRegion), "oss-%s-internal.aliyuncs.com" % (ecsRegion))
-
- # 把输入转换为unicode
- @staticmethod
- def toUnicode(data):
- if isinstance(data, bytes):
- return data.decode('utf-8')
- else:
- return data
-
- # 替换路径中的文件名;考虑分隔符为"/" 或 "\"(windows)
- @staticmethod
- def replaceFileName(filePath, replace):
- if len(filePath) <= 0 or len(replace) <= 0:
- return filePath
-
- filePath = AliyunVodUtils.urlDecode(filePath)
- separator = '/'
- start = filePath.rfind(separator)
- if start < 0:
- separator = '\\'
- start = filePath.rfind(separator)
- if start < 0:
- return None
-
- result = "%s%s%s" % (filePath[0:start], separator, replace)
- return result
-
- # 创建文件中的目录
- @staticmethod
- def mkDir(filePath):
- if len(filePath) <= 0:
- return -1
-
- separator = '/'
- i = filePath.rfind(separator)
- if i < 0:
- separator = '\\'
- i = filePath.rfind(separator)
- if i < 0:
- return -2
-
- dirs = filePath[:i]
- if os.path.exists(dirs) and os.path.isdir(dirs):
- return 0
-
- os.makedirs(dirs)
- return 1
-
-
- class AliyunVodException(Exception):
- """
- VOD上传SDK的异常类,做统一的异常处理,外部捕获此异常即可
- """
-
- def __init__(self, type, code, msg, http_status=None, request_id=None):
- Exception.__init__(self)
- self.type = type or 'UnkownError'
- self.code = code
- self.message = msg
- self.http_status = http_status or 'NULL'
- self.request_id = request_id or 'NULL'
-
- def __str__(self):
- return "Type: %s, Code: %s, Message: %s, HTTPStatus: %s, RequestId: %s" % (
- self.type, self.code, self.message, str(self.http_status), self.request_id)
-
-
- def catch_error(method):
- """
- 装饰器,将内部异常转换成统一的异常类AliyunVodException
- """
-
- @functools.wraps(method)
- def wrapper(self, *args, **kwargs):
- try:
- return method(self, *args, **kwargs)
- except ServerException as e:
- logger.error("阿里云ServerException异常, error_code: {}, error_msg:{}, status:{}, requestId:{}",
- e.get_error_code(), e.get_error_msg(), e.get_http_status(), self.__requestId)
- # 可能原因:AK错误、账号无权限、参数错误等
- raise AliyunVodException('ServerException', e.get_error_code(), e.get_error_msg(), e.get_http_status(),
- e.get_request_id())
- except ClientException as e:
- logger.error("阿里云ClientException异常, error_code: {}, error_msg:{}, requestId:{}", e.get_error_code(),
- e.get_error_msg(), self.__requestId)
- # 可能原因:本地网络故障(如不能连接外网)等
- raise AliyunVodException('ClientException', e.get_error_code(), e.get_error_msg())
- except OssError as e:
- logger.error("阿里云OssError异常, error_code: {}, error_msg:{}, status:{}, requestId:{}", e.code, e.message,
- e.status, self.__requestId)
- # 可能原因:上传凭证过期等
- raise AliyunVodException('OssError', e.code, e.message, e.status, e.request_id)
- except IOError as e:
- logger.error("阿里云IOError异常: {}, requestId:{}", traceback.format_exc(), self.__requestId)
- # 可能原因:文件URL不能访问、本地文件无法读取等
- raise AliyunVodException('IOError', repr(e), traceback.format_exc())
- except OSError as e:
- logger.error("阿里云OSError异常: {}, requestId:{}", traceback.format_exc(), self.__requestId)
- # 可能原因:本地文件不存在等
- raise AliyunVodException('OSError', repr(e), traceback.format_exc())
- except AliyunVodException as e:
- logger.error("阿里云VodException异常: {}, requestId:{}", e, self.__requestId)
- # 可能原因:参数错误
- raise e
- except Exception as e:
- logger.error("阿里云UnkownException异常: {}, requestId:{}", traceback.format_exc(), self.__requestId)
- raise AliyunVodException('UnkownException', repr(e), traceback.format_exc())
- except:
- logger.error("阿里云UnkownError异常: {}, requestId:{}", traceback.format_exc(), self.__requestId)
- raise AliyunVodException('UnkownError', 'UnkownError', traceback.format_exc())
- return wrapper
-
-
- class AliyunVodDownloader:
- """
- VOD网络文件的下载类,上传网络文件时会先下载到本地临时目录,再上传到点播
- """
-
- def __init__(self, localDir=None):
- if localDir:
- self.__localDir = localDir
- else:
- p = os.path.dirname(os.path.realpath(__file__))
- self.__localDir = os.path.dirname(p) + '/dlfiles'
-
- def setSaveLocalDir(self, localDir):
- self.__localDir = localDir
-
- def getSaveLocalDir(self):
- return self.__localDir
-
- def downloadFile(self, fileUrl, localFileName, fileSize=None):
- localPath = self.__localDir + '/' + localFileName
- logger.info("Download %s To %s" % (fileUrl, localPath))
- try:
- lsize = self.getFileSize(localPath)
- if fileSize and lsize == fileSize:
- logger.info('Download OK, File Exists')
- return 0, localPath
-
- AliyunVodUtils.mkDir(self.__localDir)
-
- err, webPage = self.__openWebFile(fileUrl, lsize)
- if err == 0:
- logger.info('Download OK, File Exists')
- webPage.close()
- return 0, localPath
-
- fileObj = open(localPath, 'ab+')
- for chunk in webPage.iter_content(chunk_size=8 * 1024):
- if chunk:
- fileObj.write(chunk)
- except Exception as e:
- logger.error("Download fail: %s" % (e))
- return -1, None
-
- fileObj.close()
- webPage.close()
- logger.info('Download OK')
- return 1, localPath
-
- def getFileSize(self, filePath):
- try:
- lsize = os.stat(filePath).st_size
- except:
- lsize = 0
-
- return lsize
-
- def __openWebFile(self, fileUrl, offset):
- webPage = None
- try:
- headers = {'Range': 'bytes=%d-' % offset}
- webPage = requests.get(fileUrl, stream=True, headers=headers, timeout=120, verify=False)
- status_code = webPage.status_code
- err = -1
- if status_code in [200, 206]:
- err = 1
- elif status_code == 416:
- err = 0
- else:
- logger.error("Download offset %s fail, invalid url, status: %s" % (offset, status_code))
- except Exception as e:
- logger.error("Download offset %s fail: %s" % (offset, e))
- err = -2
- finally:
- return err, webPage
|