python调用接口,自动上传视频到OSS

113 阅读4分钟

需求:python脚本通过调用后端接口,实现定期自动、上传目录下的所有视频到OSS

一、使用SQLite记录视频数据

思路:使用SQLite先记录目录下所有文件名称,并添加标识位(0,1)判断是否上传,
例如:未上传是0,已上传是1

下面是python代码

1. 维护一个字典,方便对数据的修改

# 视频源 目录路径
VIDEO_SOURCE_PATH = r'D:\PythonProject\xx\'

# 上传视频接口URL
UPLOAD_URL = 'http://localhost:80/xxx/upload'

# OSS 上传的ACCESS_KEY信息
OSS_ACCESS_VERIFY = {
    'access-key-id': 'xx',
    'access-key-secret': 'xx',
    'bucket-name': 'xx',
    'end-point': 'oss-cn-chengdu.aliyuncs.com'
}

# 日志模板
LOG = {
    'error': 'error: {} 上传失败!',
    'success': 'success: {} 上传成功!',
    'null': 'null: {} 上传失败, 文件不存在!',
    'has-been-uploaded': '{} 已经存在, 已取消重复上传!'
}

UPLOAD_VIDEO_MESSAGE = {
    'no-update': '--执行结果: 暂无新视频上传',
    'begin-upload': '=' * 5 + '> (2) 上传视频到 云OSS <' + '=' * 5 + '\n' + 'BEGIN:',
    'end-upload': 'END.',
    'upload-success': '--success: 上传 {} 成功!',
    'upload-error': '--error: 上传 {} 失败!',
    'upload-success-response': 'success:upload',
    'has-been-uploaded': '上传视频已经存在',
    'access-verify-error': '无效的ossAccessKeyVerify',
    'no-exist_access': 'OSS_ACCESS_VERIFY配置异常'

}

VIDEO_NAME_TO_SQLITE_MESSAGE = {
    'no-update': '--执行结果: 表格暂无更新',
    'begin-add': '=' * 5 + '> (1) 保存名称到 SQLite <' + '=' * 5 + '\n' + 'BEGIN:',
    'end-add': 'END.',
    'add-success': '--success: 添加 {} 成功!',
    'add-error': '--error: 添加 {} 失败!',
    'permission-deny': 'Permission denied',
    'error-notify': '=' * 10 + 'error 注意' + '=' * 10,
    'file-is-used': '==文件 {} 正在被使用,请关闭 {} 文件!!!!!==\n',
    'create-table-success': '--success: {} 表创建成功'

}

CREATE_VIDEO_MESSAGE_TABLE = """
    CREATE TABLE IF NOT EXISTS video_message(
       `id` INTEGER PRIMARY KEY AUTOINCREMENT,
       `video_name` TEXT,
       `monitor_name` TEXT,
       `start_time` TEXT,
       `end_time` TEXT,
       `is_upload` INTEGER)
"""

VIDEO_MESSAGE_SQL = {
    'insert': 'INSERT INTO video_message (video_name, monitor_name, start_time, end_time, is_upload) VALUES (?,?,?,?,?)',
    'select-video-name': 'SELECT video_name from video_message',
    'select-need-upload': 'SELECT * from video_message where is_upload == 0',
    'delete-by-id': 'DELETE FROM video_message WHERE id={}',
    'update-is-upload': 'UPDATE video_message SET is_upload={} WHERE id={}',
    'drop-table': 'DROP TABLE video_message'

}

VIDEO_MAX_SIZE = 1024 * 1024 * 1024

2.添加视频记录到sqlite

因为业务需求,根据视频名称可以获得数据信息:监控源、开始时间、结束时间,因此对名称以_分隔

class SaveVideoNameToSQLite:
    def __init__(self):
        """
        初始化 视频源路径
        :return:
        """
        self.video_source_path = VIDEO_SOURCE_PATH
        # 建立连接
        self.con = sqlite3.connect("sqlite3_db/upload.db")
        # 建立游标
        self.cur = self.con.cursor()
        # 建立表格
        self.cur.execute(CREATE_VIDEO_MESSAGE_TABLE)

    def __enter__(self):

        return self

    def __exit__(self, exc_type, exc_val, exc_tb):

        self.cur.close()
        self.con.close()
        print('添加结束:关闭资源')

    @staticmethod
    def split_filename(filename):
        name, ext = os.path.splitext(filename)
        field1, field2, field3 = name.split('_')
        return field1, field2, field3

    def get_dir_video_name_list(self):
        """

        :param : 文件目录路径
        :return: 获取目录下的所有文件名
        """
        return os.listdir(self.video_source_path)

    def get_sqlite_video_name_list(self):
        """

        :return list[tuple]:sqlite中的所有视频文件名,
        """

        self.cur.execute(VIDEO_MESSAGE_SQL.get('select-video-name'))
        return self.cur.fetchall()

    def need_add_video_name_list(self):
        """
        需要上传的视频文件名列表
        :return:
        """
        old_list = [name[0] for name in self.get_sqlite_video_name_list()]
        dir_list = self.get_dir_video_name_list()
        different_list = list(set(dir_list) - set(old_list))
        # 排除非mp4文件
        return [video for video in different_list if video.endswith('.mp4')]

    def delete_table(self):
        """
        删除表中所有数据,先删表后重新创建
        :return:
        """
        self.cur.execute(VIDEO_MESSAGE_SQL.get('drop-table'))
        self.cur.execute(CREATE_VIDEO_MESSAGE_TABLE)
        self.con.commit()

    def add_video_name_to_sqlite(self):
        """

        :return:
        """
        print(VIDEO_NAME_TO_SQLITE_MESSAGE.get('begin-add'))
        need_add_list = self.need_add_video_name_list()
        if len(need_add_list) == 0:
            print(VIDEO_NAME_TO_SQLITE_MESSAGE.get('no-update'))
            print(VIDEO_NAME_TO_SQLITE_MESSAGE.get('end-add'))
            return
            # 遍历文件名
        for video_name in need_add_list:
            # 检查文件后缀,如果不是.mp4,则跳过
            if video_name[-4:] != '.mp4':
                continue
            # 拆分文件名得到字段值
            monitor_name, start_time, end_time = self.split_filename(video_name)
            self.cur.execute(
                VIDEO_MESSAGE_SQL.get('insert'), (video_name, monitor_name, start_time, end_time, 0))
            print(VIDEO_NAME_TO_SQLITE_MESSAGE.get('add-success').format(video_name))
        # 提交
        self.con.commit()
        print(VIDEO_NAME_TO_SQLITE_MESSAGE.get('end-add'))

sqlite表信息如下

image.png

3.上传视频

思路:
(1)扫描sqlite,首先获取is_upload=0的数据,
(2)然后再扫描目录中的数据(确保对应数据存在)
(3)最后遍历is_upload=0的数据,如果存在则上传,上传成功后,修改is_upload=1;反之则清除其在sqlite中的记录;
(4)最后将数据写入日志
在result中,根据接口返回信息,可以根据自己的需求进行相关操作
由于OSS存储配置了AccessKey,因此需要将参数带到后端

class UploadVideoServer:

    def __init__(self):
        """

        :return:
        excel_source_path: excel路径+excel名称
        """
        self.s = requests.session()
        self.video_source_path = VIDEO_SOURCE_PATH
        # 建立连接
        self.con = sqlite3.connect("sqlite3_db/upload.db")
        # 建立游标
        self.cur = self.con.cursor()

    def __enter__(self):

        return self

    def __exit__(self, exc_type, exc_val, exc_tb):

        self.cur.close()
        self.con.close()
        self.s.close()
        print('上传结束:关闭资源')

    def get_need_upload_data(self):
        self.cur.execute(VIDEO_MESSAGE_SQL.get('select-need-upload'))
        return self.cur.fetchall()

    def get_real_time_file(self):
        """
         获取当前时刻,目录下的 .mp4格式 视频文件名称
        :return: list[video_name]
        """
        file_list = []
        for dir_path, dirs, filenames in os.walk(self.video_source_path):
            for file_name in filenames:
                if file_name[-4:] != '.mp4':
                    pass
                else:
                    file_list.append(file_name)
        return file_list

    def upload_video_to_server(self):
        """
        通过requests调用API上传附件和对应信息
        :return:
        """
        print(UPLOAD_VIDEO_MESSAGE.get('begin-upload'))
        need_upload_list = self.get_need_upload_data()
        if len(need_upload_list) == 0:
            # 如果没有待上传视频,直接退出
            print(UPLOAD_VIDEO_MESSAGE.get('no-update'))
            print(UPLOAD_VIDEO_MESSAGE.get('end-upload'))
            return

        real_video_lists = self.get_real_time_file()

        # 遍历上传
        for datas in need_upload_list:
            row_id, video_name, monitor_name, start_time, end_time, is_upload = datas
            results = ''
            if video_name in real_video_lists:
                file_type = 'video/mp4' if os.path.splitext(video_name)[1] == '.mp4' else ''
                file_path = self.video_source_path + video_name
                headers = {
                    'tenant-id': '1',
                }
                if os.path.getsize(file_path) >= VIDEO_MAX_SIZE:
                    # 文件大小超出1G,退出循环
                    print(f"--error: 文件大小超出1G,上传失败!")
                    sys.exit(0)
                upload_file = {
                    'file': (video_name, open(file_path, 'rb'), file_type)
                }
                # 传给java后端接口数据
                datas = {
                    'videoName': video_name,
                    'monitorName': monitor_name,
                    'startTime': '2023-05-01 11:11:11',
                    'endTime': '2023-05-01 11:11:11',
                    'ossAccessKeyId': OSS_ACCESS_VERIFY.get('access-key-id'),
                    'ossAccessKeySecret': OSS_ACCESS_VERIFY.get('access-key-secret'),
                    'ossBucketName': OSS_ACCESS_VERIFY.get('bucket-name'),
                    'ossEndPoint': OSS_ACCESS_VERIFY.get('end-point')
                }
                result = self.s.post(url=UPLOAD_URL, data=datas, files=upload_file, headers=headers)
                results = result.text
            else:
                r = LOG.get('null').format(video_name)
                self.write_log(r)
                # 如果为空,则删除这条记录excel
                self.cur.execute(VIDEO_MESSAGE_SQL.get('delete-by-id').format(row_id))

            if UPLOAD_VIDEO_MESSAGE.get('upload-success-response') in results:
                r = LOG.get('success').format(video_name)
                # 上传成功,修改标识符为 1
                self.cur.execute(VIDEO_MESSAGE_SQL.get('update-is-upload').format(1, row_id))
                print(UPLOAD_VIDEO_MESSAGE.get('upload-success').format(video_name))
            elif UPLOAD_VIDEO_MESSAGE.get('has-been-uploaded') in results:
                r = LOG.get('has-been-uploaded').format(video_name)
                # 上传视频已经存在,修改标识符为 1
                self.cur.execute(VIDEO_MESSAGE_SQL.get('update-is-upload').format(1, row_id))
                print(f'--notify: {video_name}已存在!')
            elif UPLOAD_VIDEO_MESSAGE.get('access-verify-error') in results:
                print(UPLOAD_VIDEO_MESSAGE.get('access-verify-error'))
                sys.exit(0)
            elif UPLOAD_VIDEO_MESSAGE.get('no-exist_access') in results:
                print('--error: ' + UPLOAD_VIDEO_MESSAGE.get('no-exist_access') + "!!!")
                sys.exit(0)
            else:
                r = LOG.get('error').format(video_name)
                # 上传失败,不修改文件,下一次遍历时会再上传
                print(UPLOAD_VIDEO_MESSAGE.get('upload-error').format(video_name))
            self.write_log(r)
            # 保存sqlite
            self.con.commit()
        print(UPLOAD_VIDEO_MESSAGE.get('end-upload'))

    @staticmethod
    def write_log(r):
        """
        记录接口调用日志
        :param r: 日志信息
        :return:
        """
        with open('report/log.txt', 'a', encoding='utf-8') as f:
            f.write(r + '\n')

4.函数入口

首先判断相关文件夹是否存在,没有存在则创建
使用with创建上下文环境,确保代码块结束后正确关闭资源
在while循环中,设置没30分钟执行一次

if __name__ == '__main__':
    # 判断视频源文件是否存在
    if not os.path.exists(VIDEO_SOURCE_PATH):
        print(f"严重错误:视频源目录{VIDEO_SOURCE_PATH}不存在,请检查目录是否配置正确!!!")
        print(f"严重错误:视频源目录{VIDEO_SOURCE_PATH}不存在,请检查目录是否配置正确!!!")
        sys.exit(0)
    # 判断文件夹是否存在
    file_names = ['sqlite3_db', 'report']
    for file_name in file_names:
        if not os.path.exists(file_name):
            # 创建文件夹
            os.makedirs(file_name)
            print(f"{file_name}文件夹已创建")
        else:
            print(f"{file_name}文件夹已存在")

    # 开始循环
    with UploadVideoServer() as upload_server, SaveVideoNameToSQLite() as save_sqlite:
        while True:
            print('*' * 20 + 'begin' + '*' * 20)
            # 添加新的video_name 到sqlite
            save_sqlite.add_video_name_to_sqlite()
            # 执行上传
            upload_server.upload_video_to_server()
            print('*' * 21 + 'end' + '*' * 21)
            print('等待下一次上传开始......')
            # 暂停30分钟
            time.sleep(30 * 60)

5.spring boot后端接口

使用MultipartFile file接收python传来的二进制文件,因为框架中需要上传byte数据,因此做了转化(根据需求转化)

@PostMapping("/upload-oss")  
@Operation(summary = "上传视频到OSS")  
public CommonResult<String> uploadVideoToOss(@RequestParam("file") MultipartFile file,  MonitorVideoCreateReqVO createReqVO,  OssAccessVerify ossAccessVerify) {  
    // 代码逻辑
    byte[] content = file.getBytes();
    ...
}

6.运行结果

(1)添加成功

image.png (2)暂无更新


(3)等待上传

image.png

参考文档:

后端详细文档:blog.csdn.net/zhouhengzhe…