zoukankan      html  css  js  c++  java
  • 最终屏幕录制方案

    屏幕录制方案

    需求

    • 实现任务录制任务下发后自动将动画和音频录制为MP4的视频
    • 后台服务
    • 录制进度实时更新
    • 后续分享到视频播放平台,如爱奇艺、抖音等

    方案架构

    • django command启动服务
    • gearman提交下发任务
    • 后端采用PyQt5搭建服务平台
    • QProcess执行ffmpeg录制屏幕命令
    • QThread维持gearmanworker接受任务
    • WebDriver加载定制页面播放动画及音频

    前端

    • 实现动画加载,图片按浏览器宽高比率放置
    • 加载完成之后修改特定标签文本值
    • 点击事件触发以后播放音频,后续页依次自动播放
    • 整个动画文件播放完毕以后修改特定标签文本值

    后端

    • 搭建服务平台
    • 执行ffmpeg录制屏幕
    • 维持gearmanworker运转,接收屏幕录制任务
    • 监测前端录制进程,控制录制的开始和结束
    • 实时上传录制进度
    • 上传视频至cos

    录制流程图

    录制流程说明

    搭建服务平台

    django manage 启动任务,初始化PyQT5搭建的服务平台

    class Command(BaseCommand):

        def handle(self, *args, **options):
            app = QApplication(sys.argv)
            win = MainWindow()
            app.exit(app.exec_())

    在PyQt5平台中初始化

     参数(init_arguments)、搭载gearmanworker的(init_worker),WebDriver(init_driver),任务加载定时器(init_timer)

    class MainWindow(QMainWindow):

        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
            self.root_path = Path(__file__).parent
            self.setWindowTitle('易哈佛')
            self.init_arguments()
            self.init_driver()
            self.init_worker()
            self.init_timer()

    注册gearmanworker

    QThread中注册gearmanworker到gearman服务器,接收到的任务都存入任务池中

    class WebWorker(QThread):

        def __init__(self):
            super().__init__()
            self.worker = None
            self.task_pool = []

        def run(self):
            self.worker = self.init_worker()
            self.worker.work()

        def init_worker(self):
            convert_worker = JsonWorker(['c.ehafolive.com:4730', ])
            convert_worker.set_client_id('capture')
            convert_worker.register_task('screencapture'self.task_listener)
            return convert_worker

        @property
        def worker_(self) -> JsonWorker:
            return self.worker

        def task_listener(self, gearman_worker, request):
            self.task_pool.append(request.data)

    任务加载定时器

    任务加载定时器会每五秒调起一次任务加载功能。任务加载功能必须在任务池存在任务,并且当前平台没有任务时才会执行任务加载,为了避免并发导致任务冲突,所以任务加载功能单线程执行。

    def load_url(self):
        # 接受视频录制任务
        self.lock.acquire()
        if self.WebWorker.task_pool and self.usable:
            self.usable = False
            info = self.WebWorker.task_pool.pop(0)
            self.video_layout = info.get('layout')
            self.h_id = int(info.get('pk'))
            print(self.video_layout, self.h_id)
            self.driver_load()
        self.lock.release()

    浏览器加载功能

    一但允许任务加载从任务池中提取一个任务,提取出数据,调用浏览器加载功能,浏览器加载功能是通过加载定制的页面,并通过设置浏览器的位置和宽高来大致确定后续需要录制的区域,在页面加载完毕后,需要初始化录制动作监控定时器,并初始化一个视频对象,后续的进度实时更新依赖于初始化的视频对象

    def driver_load(self):
        # 设置浏览器位置及窗口大小,加载url
        url = "https://xxxx?pk=%s" % self.h_id
        if self.video_layout == 'vertical':
            width, height = 10001446
        else:
            width, height = 1373800
        self.set_driver(width, height)
        self.driver.get(url)
        self.init_cmd_timer()
        self.init_video_instance()

    浏览器设置

    通过对任务的横版和竖版的判定,将浏览器位置调整到合适的区域,并设置相应的宽高

    def set_driver(self, width, height):
        if self.video_layout == 'vertical':
            left, top = self.chose_screen(7681366)
        else:
            left, top = self.chose_screen(1366768)
        self.screen_width = width - self.window_offset_x
        self.screen_height = height - self.window_offset_y
        self.driver.set_window_position(left - self.window_offset_x, top)
        self.driver.set_window_size(width, height)

    视频对象的初始化

    因为任务可能重复下发,因此视频对象可能早有存在,所以对于已存在的对象只需局部更新数据,不需要另外创建

    def init_video_instance(self):
        from article.models import Article
        from points.models import Video
        queryset = Article.objects.filter(id=self.h_id)
        if not queryset.exists():
            return
        article = queryset.first()
        data = {
            'size'0,
            'process'0,
            'course'self.h_id,
            'user': article.user,
            'layout': article.layout,
            'duration': article.duration,
        }
        video, _ = Video.objects.update_or_create(
            defaults=data,
            course=self.h_id,
        )
        self.video_id = video.id

    录制动作监控定时器超时事件响应

    在上述任务加载完毕,并且初始化完成后,录制动作监控定时器将迎来第一次的超时事件,超时事件将检测相应的标签值,在确定标签值已经改变并由后端完成修改后,将调起相应的功能

    #录制动作监控定时器超时调起事件
    def monitor_cmd(self):
        if self.set_monitor_tag_value('click'):
            self.mouse_click_event()
        if self.set_monitor_tag_value('start'):
            self.start_capture()
        if self.set_monitor_tag_value('stop'):
            self.stop_capture()
    #监测动作对应的标签文本值,发生变化则进行修改
    def set_monitor_tag_value(self, tag):
        lock = Lock()
        lock.acquire()
        _, value = self.get_monitor_tag_value(tag)
        if value == 'false':
            lock.release()
            return False
        attributeName = 'textContent'
        js = "{}.{}={}".format(tag, attributeName, 'false')
        self.driver.execute_script(js)
        print('set element value %s' % tag)
        lock.release()
        return True

    click标签文本值改变关联事件

    由后端在前端确定url加载完毕后调起,实现模拟点击事件,通知前端开始播放动画和音频

    def mouse_click_event(self):
        # 点击命令执行,开始播放
        self.driver.find_element_by_id('click-element').click()

    start标签文本值改变关联事件

    start标签值的改变在前端确定动画和音频已经开始播放后修改,当后端检测到文本值发生改变后,检测录制区域是否超出屏幕、初始化录制视频进程、初始化录制进度更新定时器,开始屏幕录制

    def start_capture(self):
        # 开始录制视频
        if not self.verify_capture_size():
            return self.unlocked()
        capture_cmd = self.init_capture_cmd()
        self.process = RecordProcess()
        self.process.start(capture_cmd)
        self.init_process_timer()
        print(capture_cmd)

    stop标签文本值改变关联事件

    start标签值的改变在前端确定动画和音频已经播放完毕后修改,当后端检测到文本值发生改变后,调起停止视频录制功能,关闭录制动作监控定时器,关闭进度更新定时器,待录制视频进程完全结束,视频文件生成后调起上传视频

    def stop_capture(self):
        # 停止录制视频
        if not hasattr(self'process'): return
        self.process.quit()
        self.process_timer.stop()
        self.monitor_timer.stop()
        self.process.finished.connect(self.upload_video)

    视频上传功能

    上传视频到cos,上传完成后,解除任务正在执行的标志,等待下一次任务加载,同时调起顶顶发送消息通知任务已经完成

    def upload_video(self):
        # 上传视频到数据库
        url = save_video(self.video_path, self.video_id)
        self.usable = True
        task_success(TASK, "path:%s" % url)

    上传视频

    如果已经录制过视频,原视频从cos删除

    def save_video(video_path, pk):
        if not os.path.exists(video_path):
            print('视频转录异常', video_path)
            return
        f = open(video_path, 'rb')
        url = upload_video(f.name, f)
        f.seek(0)
        size = len(f.read())
        instance = Video.objects.get(id=pk)
        if instance.url:
            delete_from_cos(instance.url.url)
        instance.url = url
        instance.size = size
        instance.process = 100
        instance.save()
        print(url, pk)
        print('*' * 100)
        f.close()
        return url

    ​​

  • 相关阅读:
    IO模式和IO多路复用详解
    消息队列RabbitMQ、缓存数据库Redis
    rest framework认证组件和django自带csrf组件区别详解
    django进阶之缓存
    关于CSRF攻击详解
    Linux学习常用命令大全
    .NET 开源工作流: Slickflow流程引擎基础介绍(四) -- 多数据库支持实现
    .NET 开源工作流: Slickflow流程引擎基础介绍(三) -- 基于HTML5/Bootstrap的Web流程设计器
    .NET 开源工作流: Slickflow流程引擎基础介绍(二) -- 引擎组件和业务系统的集成
    .NET开源敏捷开发框架: SlickOne介绍(一) -- 基于Dapper, Mvc和WebAPI 的快速开发框架
  • 原文地址:https://www.cnblogs.com/li1992/p/11057197.html
Copyright © 2011-2022 走看看