AIlib2/voduploadsdk/AliyunVodUtils.py

326 lines
11 KiB
Python
Raw Normal View History

2025-04-26 10:35:59 +08:00
# -*- coding: UTF-8 -*-
import os,sys
import hashlib
import datetime
import functools
import logging
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)
logger = AliyunVodLog
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:
# 可能原因AK错误、账号无权限、参数错误等
raise AliyunVodException('ServerException', e.get_error_code(), e.get_error_msg(), e.get_http_status(), e.get_request_id())
logger.error("ServerException: %s", e)
except ClientException as e:
# 可能原因:本地网络故障(如不能连接外网)等
raise AliyunVodException('ClientException', e.get_error_code(), e.get_error_msg())
logger.error("ClientException: %s", e)
except OssError as e:
# 可能原因:上传凭证过期等
raise AliyunVodException('OssError', e.code, e.message, e.status, e.request_id)
logger.error("OssError: %s", e)
except IOError as e:
# 可能原因文件URL不能访问、本地文件无法读取等
raise AliyunVodException('IOError', repr(e), traceback.format_exc())
logger.error("IOError: %s", traceback.format_exc())
except OSError as e:
# 可能原因:本地文件不存在等
raise AliyunVodException('OSError', repr(e), traceback.format_exc())
logger.error("OSError: %s", traceback.format_exc())
except AliyunVodException as e:
# 可能原因:参数错误
raise e
logger.error("VodException: %s", e)
except Exception as e:
raise AliyunVodException('UnkownException', repr(e), traceback.format_exc())
logger.error("UnkownException: %s", traceback.format_exc())
except:
raise AliyunVodException('UnkownError', 'UnkownError', traceback.format_exc())
logger.error("UnkownError: %s", 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