最終屏幕錄製方案

屏幕錄製方案

需求

  • 實現任務錄製任務下發後自動將動畫和音頻錄製爲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)python

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)

任務加載定時器

任務加載定時器會每五秒調起一次任務加載功能。任務加載功能必須在任務池存在任務,而且當前平臺沒有任務時纔會執行任務加載,爲了不併發致使任務衝突,因此任務加載功能單線程執行。django

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)

視頻對象的初始化

由於任務可能重複下發,所以視頻對象可能早有存在,因此對於已存在的對象只需局部更新數據,不須要另外建立ruby

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

​​

相關文章
相關標籤/搜索