PyQt QGraphicsScene Рендеринг сцены в формате видео

Я пытаюсь разрешить пользователю экспортировать сцену в формате mp4 (видеоформат), элементы сцены состоят из QGraphicsVideoItem и нескольких QGraphicsTextItem, мне нужно экспортировать сцену, поскольку это позволит пользователю сохранить видео с помощью текстовые элементы. Я нашел один из способов сделать это, но проблема в том, что для создания простого 5-секундного видео потребуются часы, поскольку для создания видео каждое изображение сохраняется в байте, каждое изображение занимает миллисекунду. Если я перейду с миллисекунд на секунды, скорость может увеличиться, но видео не будет выглядеть таким плавным. Есть ли более эффективный способ сделать это, не занимая так много времени?

from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
from PySide6.QtSvgWidgets import *
from PySide6.QtMultimediaWidgets import QGraphicsVideoItem
from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput, QMediaMetaData 

import subprocess

import sys

class ExportVideo(QThread):
    def __init__(self, video_item, video_player, graphics_scene, graphics_view):
        super().__init__()
        self.video_item = video_item
        self.video_player = video_player
        self.graphics_scene = graphics_scene
        self.graphics_view = graphics_view
    
    def run(self):
        self.video_player.pause()
        duration = self.video_player.duration()
        meta = self.video_player.metaData()


        # Prepare a pipe for ffmpeg to write to
        ffmpeg_process = subprocess.Popen(['ffmpeg', '-y', '-f', 'image2pipe', '-r', '1000', '-i', '-', '-c:v', 'libx265', '-pix_fmt', 'yuv420p', 'output.mp4'], stdin=subprocess.PIPE)

        for duration in range(0, duration):
            self.video_player.setPosition(duration)

            # Add logic to render the frame here
            print("Exporting frame:", duration) 

            image = QImage(self.graphics_scene.sceneRect().size().toSize(), QImage.Format_ARGB32)
            painter = QPainter(image)
            self.graphics_scene.render(painter)
            painter.end()

            # Convert QImage to bytes
            byte_array = QByteArray()
            buffer = QBuffer(byte_array)
            buffer.open(QIODevice.WriteOnly)
            image.save(buffer, 'JPEG')

            # Write image bytes to ffmpeg process
            ffmpeg_process.stdin.write(byte_array.data())

        # Close the pipe to signal ffmpeg that all frames have been processed
        ffmpeg_process.stdin.close()
        ffmpeg_process.wait()


class PyVideoPlayer(QWidget):
    
    def __init__(self):
        super().__init__()

        self.text_data = []

        self.mediaPlayer = QMediaPlayer()
        self.audioOutput = QAudioOutput()

        self.graphics_view = QGraphicsView()
        self.graphic_scene = QGraphicsScene()

        self.graphics_view.setScene(self.graphic_scene)
        self.graphic_scene.setBackgroundBrush(Qt.black)
        self.graphics_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.graphics_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        self.video_item = QGraphicsVideoItem()
        self.graphic_scene.addItem(self.video_item)
        self.save_video = QPushButton()
        
        layout = QVBoxLayout()
        layout.addWidget(self.graphics_view, stretch=1)
        layout.addWidget(self.save_video)
        self.setLayout(layout)

        # Slots Section
        self.mediaPlayer.setVideoOutput(self.video_item)
        self.mediaPlayer.positionChanged.connect(self.changeVideoPosition)
        self.save_video.clicked.connect(self.saveVideo)

    def setMedia(self, fileName):
        self.mediaPlayer.setSource(QUrl.fromLocalFile(fileName))
        self.mediaPlayer.setAudioOutput(self.audioOutput)
        self.play()
        self.video_item.setSize(self.mediaPlayer.videoSink().videoSize())

        self.text_item = QGraphicsTextItem()
        self.text_item.setPlainText("Test Dummy")
        self.text_item.setDefaultTextColor(Qt.white)
        font = QFont()
        font.setPointSize(90)  
        self.text_item.setFont(font)
        self.text_item.setPos(self.graphic_scene.sceneRect().x() + self.text_item.boundingRect().width(), self.graphic_scene.sceneRect().center().y() - self.text_item.boundingRect().height())
        self.graphic_scene.addItem(self.text_item)
        self.text_data.append("Test Dummy")

    def play(self):
        if self.mediaPlayer.playbackState() == QMediaPlayer.PlaybackState.PlayingState:
            self.mediaPlayer.pause()
        else:
            self.mediaPlayer.play()

    def changeVideoPosition(self, duration):
        if duration > 1000 and self.text_item.isVisible():
            print("Hide Text")
            self.text_item.hide()

    def resize_graphic_scene(self):
        self.graphics_view.fitInView(self.graphic_scene.sceneRect(), Qt.KeepAspectRatio)

    def showEvent(self, event):
        self.resize_graphic_scene()

    def resizeEvent(self, event):
        self.resize_graphic_scene()

    def saveVideo(self):
        self.videoExport = ExportVideo(self.video_item, self.mediaPlayer, self.graphic_scene, self.graphics_view)
        self.videoExport.start()

    


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = PyVideoPlayer()
    window.setMedia("example.mp4")
    window.setGeometry(100, 100, 400, 300)  # Set the window size
    window.setWindowTitle("QGraphicsView Example")
    window.show()
    sys.exit(app.exec())

  • Обновлено: изменен с PNG на JPEG, это ускоряет изображение. Сохраните, у меня также возникла эта проблема: Assertion fctx->async_lock failed at C:/ffmpeg-n6.0/libavcodec/pthread_frame.c:155. Я думаю, это должно быть связано с настройкой положения видеоплеера.

Я заметил, что image.save(buffer, 'PNG') - это процесс, который занимает больше времени и замедляет весь процесс. Если есть более эффективный способ получения байтовых массивов изображения, можно ускорить создание видео. , Я думаю.

Alex 04.05.2024 20:32

Текст фиксирован на время?

kesh 04.05.2024 21:47

Оба подхода совершенно неэффективны. Если пользователь ничего не делает в течение нескольких секунд (или даже минут/часов), обработка сохранит тысячи одинаковых изображений, неоправданно занимая память и процессор. Более подходящая концепция могла бы хранить пару данных с использованием (монотонной) метки времени и соответствующего отображаемого контента, обновляемого вместе с сигналом QGraphicsScene.changed. Поскольку вам не нужен рендеринг в реальном времени, вы можете завершить вывод с помощью соответствующих команд ffmpeg, используя различные «ключевые кадры» в зависимости от их временных меток.

musicamante 04.05.2024 21:52

Не могли бы вы показать мне пример улучшений, которые вы предлагаете? Текст не будет исправлен, поскольку у него будет время начала и окончания в зависимости от времени видео, а позже появится анимация.

Alex 04.05.2024 22:04

Я не знаю, как с этим справиться на стороне Qt, но наиболее эффективный подход — сделать снимки только текстовых объектов (индивидуально) в виде изображений и наложить их на видео в FFmpeg с его графом фильтров. Таким образом, вы не будете тратить время на захват видеокадров. Я подготовлю ответ позже, если этот подход заинтересует.

kesh 04.05.2024 22:58

Еще один вопрос. Вам важно, чтобы формат текста (шрифт, размер и т.д.) соответствовал тому, что на экране? Если нет, вы можете просто использовать фильтр drawtext.

kesh 04.05.2024 23:13

Да, пользователь сможет изменить формат текста, и текст будет разным.

Alex 04.05.2024 23:23

@kesh, я пытался сделать то, что вы предлагали раньше, но продолжал получать сообщение об ошибке и вернулся к захвату видео и текста. Я думаю, способ, который вы предлагаете, вероятно, будет более эффективным.

Alex 04.05.2024 23:50

@Alex Еще одна вещь, которая имеет чрезвычайную важность: элементы пользовательского интерфейса не являются потокобезопасными. Их не только нельзя создать или «записать» (изменить их свойства) из отдельных потоков, но и доступ к ним в «режиме чтения» по-прежнему небезопасен и совершенно ненадежен. Кроме того, воспроизведение видео может быть очень требовательным, а это означает, что попытка захватить все содержимое сцены может привести к задержкам не только записи, но и воспроизведения. Python не очень хорошо справляется с требовательными задачами в реальном времени, поэтому у вас есть только два варианта: 1. если вам не нужна идеально плавная запись, то »

musicamante 05.05.2024 04:14

@Alex » просто используйте комбинацию QTimer и вышеупомянутого сигнала changed в качестве оптимизации; это все равно приведет к потенциальным задержкам при воспроизведении приложения во время записи, если ЦП не сможет справиться с этой задачей; 2. если вам нужно более безопасное (всё ещё в зависимости от ЦП) плавное воспроизведение и запись, то придётся переключаться на многопроцессорность или совсем на другой язык. В любом случае остается то, что объяснялось выше: 1. непрерывная запись не имеет смысла; 2. вы не должны использовать потоки для рендеринга виджета.

musicamante 05.05.2024 04:18

Я думаю, что у меня может быть лучший способ экспортировать видео, поскольку пользователь не сможет изменить видео только отображаемый текст, я пытаюсь сделать так, чтобы он захватывал каждый текстовый формат и время начала/окончания. , который будет использовать эту информацию для создания текста поверх видео с помощью drawtext из ffmpeg. Вероятно, будут ограничения по настройке, но я думаю, что это будет более эффективно, чем использование функции рендеринга из сцены, и не будет так требовательно к процессору.

Alex 05.05.2024 14:40

Можете ли вы просто сохранить текстовый графический объект в формате PNG с прозрачным фоном? Я думаю, это было бы лучшее из обоих миров

kesh 05.05.2024 16:38

Я пробовал это, и, поскольку на заднем плане есть видео, оно экспортирует текст и видео, поскольку единственный способ рендеринга, который я знаю, - это с помощью Scene.render, и он визуализирует все, что находится в пределах ваших границ. Я запросил рендеринг, поэтому, если за текстом что-то есть, он также это визуализирует. Я пытаюсь внести изменения, предложенные musicamante, и если я не смогу, мне, возможно, придется придерживаться вашего предложения или написать текст непосредственно в видео.

Alex 05.05.2024 16:48

@musicamante воспроизведение и экспорт видео не обязательно должны происходить одновременно, если вы подразумеваете это под «воспроизведением и записью». На данный момент у меня есть поток, меняющий положение видео, чтобы текст знал, когда больше не показывать и воспроизводить следующую часть видео. Однако, если не требуется устанавливать положение видео для захвата видеокадра и текста, не могли бы вы сообщить мне, как это сделать, спасибо.

Alex 05.05.2024 23:04

Извините, судя по вашему первоначальному вопросу, вы хотели сделать «скриншот» этой сцены. На самом деле вы просто хотите добавить элементы в видео, а это значит, что рендеринг QGraphicsScene не только бессмысленен, но и совершенно неверен. Вам следует сохранить данные логического объекта, которые вы хотите нарисовать (геометрические элементы, содержимое, внешний вид временной шкалы и т. д.), и в конечном итоге экспортировать их, используя исходное видео, а не элемент видеографики.

musicamante 05.05.2024 23:31
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
15
140
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я думаю, что проблема, с которой вы столкнетесь при нынешнем подходе (снимок экрана пользовательского интерфейса для создания аннотированного видео), заключается в том, что вы, скорее всего, потеряете исходное разрешение видео (скажем, если оно 1080p, но отображается в кадре пользовательского интерфейса 300x400, тогда вы закончите с видео 300x400, а не 1080p).

Лучшим подходом IMO является создание текстового изображения (PNG) с прозрачным фоном в FFmpeg (или любым другим способом) с соответствующим разрешением кадра (например, 1080p в моем примере) и загрузка каждого из таких изображений в QGraphicScene как QGraphicItem ( который, как я полагаю, можно перетаскивать, чтобы изменить положение).

Это даст вам фоновое видео и несколько текстовых изображений с указанием времени их отображения и смещения положения. Затем FFmpeg может объединить эти файлы вместе.

Чтобы сгенерировать текстовый PNG, вы можете запустить

import subprocess as sp

video_size = [1920, 1080]
text = "hello world"
color = "black"
fontsize = 30
fontfile = "Freeserif.ttf"

textfile = "temp_01.png" # place it in a temp folder & increment

sp.run(
    [
        "ffmpeg",
        "-f", "lavfi",
        "-i", f"color=c = {color}@0:size = {video_size[0]}x{video_size[1]},format=rgba",
        "-vf", f'drawtext=fontsize = {fontsize}:fontfile = {fontfile}:text=\'{text}\':x=(w-text_w)/2:y=(h-text_h)/2',
        "-update", "1", "-vframes", "1",
        "-y", # if needed to overwrite old
        textfile,
    ]
)

Этот скрипт создает PNG с надписью «Hello World» в центре экрана. Допустим, мы хотим поместить этот текст между временными метками 1 и 2 секунды видео с заданным пользователем смещением [-200px,100px] (в направлении нижнего левого угла).


text_start = 1
text_end = 2
text_xoff = -200
text_yoff = 100

videofile = "example.mp4"
outfile = "annotated.mp4"

sp.run(
    [
        "ffmpeg",
        "-i", videofile,  # [0:v]
        "-i", textfile,  # [1:v]
        "-filter_complex", f"[0:v][1:v]overlay=x = {text_xoff}:y = {text_yoff}:enable='between(t,{text_start},{text_end})'[out]",
        "-map", "[out]",
        '-y', # again, if need to overwrite previous output
        outfile,
    ]
)

Если у вас есть несколько текстовых изображений для наложения, вам необходимо указать все текстовые файлы в качестве дополнительных входных данных (-i ...) и каскадно запустить фильтры наложения:

[0:v][1:v]overlay=...[out1];
[out1][2:v]overlay=...[out2];
...
[outN][N:v]overlay=...[out]

Очевидно, вы хотите сгенерировать выражение графа фильтра программным способом.

Вот ссылки на используемые фильтры цвет , формат , drawtext , наложение. Ознакомьтесь с этими фильтрами и конструкцией графа фильтров в целом (см. верхнюю часть связанной страницы документа), особенно обратите внимание на экранирование символов. (подсказка: поместите текст в одинарные кавычки и избегайте одинарных кавычек в наложенном тексте)

Примечания

  • если текст слишком длинный, он обрезается за пределы видеокадра
  • как только вы запустите этот механизм, вы сможете проверить, могут ли Qt использовать передаваемые по конвейеру данные PNG. FFmpeg поддерживает кодирование base64 с использованием протокола данных.
  • overlay фильтр поддерживает перемещение текстового изображения во времени, поэтому его можно анимировать, но отображение анимации на Qt было бы затруднительно
  • Я думаю, что существует ограничение на длину команды подпроцесса. Таким образом, при таком подходе вы можете достичь предела, особенно если вы кодируете данные PNG.

Я не знаком с Qt-концом бизнеса. Итак, я оставлю это вам.

Не стесняйтесь задавать вопросы в разделе комментариев.

Я думаю, поскольку текст создается в изображении, а затем добавляется в видео, я мог бы просто нарисовать текст непосредственно в видео? Я не уверен, как я буду поддерживать формат, например, некоторые символы в тексте QGraphicsTextItem имеют разные цвета, жирность и т. д. Однако, вероятно, есть способ сделать это.

Alex 05.05.2024 15:04

Вы, конечно, можете сразу приступить к записи текста на видео, но у меня сложилось впечатление, что вы хотите, чтобы пользователь мог размещать/форматировать текст в пользовательском интерфейсе. Здесь я предлагаю вместо того, чтобы позволить Qt визуализировать текст, визуализировать его с помощью FFmpeg и отобразить результат в виде плавающих изображений на холсте с видео в качестве задней панели, если это имеет смысл.

kesh 05.05.2024 15:53

Кое-что из этого поста. Тогда вы все равно сможете перемещать текст в Qt. Однако необходимо создать новое текстовое изображение, если пользователь хочет изменить текстовый формат.

kesh 05.05.2024 15:54

Я должен был быть более ясным: пользователи должны иметь возможность регулярно менять формат и положение текста (аналогично видеоредактору), а создавать новый текст изображения каждый раз, когда им нужно что-то изменить, было бы неэффективно, я изучаю что @musicamante предложил по оптимизации с помощью многопроцессорности, однако спасибо за предложение.

Alex 05.05.2024 16:19

Попался. Возможно, это не так дорого, как вы думаете. Я бы предложил попробовать генерацию изображения, чтобы увидеть его скорость, прежде чем отказываться от этого пути. Удачи!

kesh 05.05.2024 16:32
Ответ принят как подходящий

Исправление, которое я нашел, не использует PyQt для основного экспорта видео, а использует только FFMPEG. Я знал, что FFMPEG не поддерживает формат Rich Text Format (RTF), поэтому начал искать другие возможные способы решения этой проблемы. Я обнаружил, что вы можете использовать формат файла под названием Advanced SubStation Alpha (.ASS), который позволяет вам контролировать все, что угодно. делать с текстом, например шрифт, размер, цвет, время начала/окончания и многое другое.

При использовании .ASS вам необходимо убедиться, что вы правильно форматируете файл .ASS, иначе FFMPEG может не записать субтитры так, как вы хотите. Вам также понадобится текст, который вы хотите отобразить в видео, в формате HTML, что можно сделать с помощью pyqt text.toHtml().

Когда у вас есть путь к местоположению текстовой версии HTML, вам также понадобятся размер/цвет обводки и начало/конец текста. Вы также можете отредактировать его, включив в него положение текста. Вам придется вызвать функцию с исходным видео.

from bs4 import BeautifulSoup
import subprocess, sys
from PySide6.QtCore import *
from PySide6.QtWidgets import *

class videoExport(QThread):
    def __init__(self, text_and_stroke, video_location):
        super().__init__()
        self.text_and_stroke = text_and_stroke
        self.video_location = video_location

    def run(self):
        dialogues = []

        for html_file_path, pos_x, pos_y, stroke_size, stroke_color, start, end in self.text_and_stroke:
            with open(html_file_path, 'r') as file:
                html_content = file.read()
                
            soup = BeautifulSoup(html_content, 'html.parser')
            body_tag = soup.find('body')


            if body_tag:
                self.styles_attributes = self.retrieve_text_style(body_tag.get('style'))
                text_style = self.text_styles(soup, stroke_size, stroke_color)

                self.styles_attributes = ",".join(self.styles_attributes)
                text_style = "".join(text_style)

                new_dialogue = f"""Dialogue: {start},{end},Default,{{\pos({pos_x},{pos_y})}}{text_style}"""
                dialogues.append(new_dialogue)

            file.close()


        create_ass_file = f"""[Script Info]
Title: Video Subtitles
ScriptType: v4.00+
Collisions: Normal
PlayDepth: 0

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BorderStyle, Encoding
Style: Default, {self.styles_attributes},&HFFB0B0,&HFFFF00,&H998877,0,0
Style: Background, {self.styles_attributes},&H00FFFFFF,&H000000FF,&H00000000,3,0

[Events]
Format: Start, End, Style, Text
"""
        
        for dialogue in dialogues:
            create_ass_file += f"{dialogue}\n"

        with open("new_ass.ass", 'w') as file:
            file.write(create_ass_file)

        file.close()

        self.add_subtitle_to_video(self.video_location, "new_ass.ass")

    
    def add_subtitle_to_video(self, video_file, ass_file):
        video_text = [
            "ffmpeg",
            "-y",
            "-i", video_file,
            "-vf", f"subtitles = {ass_file}",
            "-c:a", "copy",
            "output.mp4"
        ]
        process = subprocess.Popen(video_text, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,universal_newlines=True)


    # ADDS ANY TEXT STYLE TO THE TEXT
    # ///////////////////////////////////////////////////////////////

    def text_styles(self, soup, stroke_size, stroke_color):
        text_styles = []
        paragraph = soup.find_all('p')
        span_elements = soup.find_all('span')

        if paragraph:
            for paragraph_element in paragraph:
                for content in paragraph_element.contents:
                    if isinstance(content, str):
                        written_text = content.strip()
                        text_styles.append(written_text)


        if span_elements:

            for span in span_elements:

                style_attr = span.get('style')
                style_and_name = style_attr.split(';')

                # Initialize variables to hold style modifications
                italic_str = underline_str = ""
                color_str = background_color_str = text_stroke = ""
                font_weight_str = font_size_str = ""
                font_family_str = ""

                for text in style_and_name:
                    if ':' in text:
                        style = text.split(':')[0].strip()
                        style_name = text.split(':')[1].strip().replace("'", '')

                        if stroke_size > 0:
                            stroke_color = stroke_color.replace('#', '')
                            red = int(stroke_color[0:2], 16)
                            green = int(stroke_color[2:4], 16)
                            blue = int(stroke_color[4:6], 16)
                            text_stroke = f"\\bord{stroke_size}\\3c&H{blue:02X}{green:02X}{red:02X}&"

                        # Modify text based on style
                        if style_name == 'italic':
                            italic_str = "{\\i1}"

                        elif style_name == 'underline':
                            underline_str = "{\\u1}"

                        elif style == 'color':
                            hex_color = style_name.replace('#', '')
                            red = int(hex_color[0:2], 16)
                            green = int(hex_color[2:4], 16)
                            blue = int(hex_color[4:6], 16)
                            color_str = f"\\c&H{blue:02X}{green:02X}{red:02X}&"

                        elif style == 'background-color' and stroke_size <= 0:
                            hex_color = style_name.replace('#', '')
                            red = int(hex_color[0:2], 16)
                            green = int(hex_color[2:4], 16)
                            blue = int(hex_color[4:6], 16)
                            background_color_str = f"\\rBackground\\bord1\\3c&H{blue:02X}{green:02X}{red:02X}&"

                        elif style == 'font-weight':
                            font_weight_str = "{\\b1}"

                        elif style == 'font-size':
                            font_size = style_name.replace('pt', '')
                            font_size_str = f"\\fs{font_size}"
                        
                        elif style == 'font-family':
                            font_family_str = f"\\fn{style_name}"

                        
                        # Combine all style modifications
                        text_style = "{" + text_stroke + background_color_str + color_str + italic_str + underline_str + font_size_str + font_family_str + font_weight_str + "}"  
                        text_with_styles = f"{text_style}{span.text.strip()}{{\\r}}"
            
                # Append modified text to the list
                text_styles.append(text_with_styles)

        return text_styles
    

    # Retrive the basic text style attributes
    # ///////////////////////////////////////////////////////////////

    def retrieve_text_style(self, style_attr):
        style_and_name = style_attr.split(';')
        style_attributes = []
        
        for text in style_and_name:
            if ':' in text:
                style_name = text.split(':')[1].strip().replace("'", '')
                if 'pt' in style_name:
                    style_name = style_name.replace('pt', '')
                    style_attributes.append(style_name)
                    break
                
                style_attributes.append(style_name)
        
        return style_attributes
    

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Video Exporter")

        self.export_button = QPushButton("Export Video")
        self.export_button.clicked.connect(self.export_video)

        layout = QVBoxLayout()
        layout.addWidget(self.export_button)

        central_widget = QWidget()
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)

    def export_video(self):
        text_and_stroke = [("new_subtitle.html", "0", "0", 10, "#FFFFFF", "0:00:00.00", "0:00:05.00")]
        self.export_thread = videoExport(text_and_stroke, "other.mp4")
        self.export_thread.finished.connect(self.on_export_finished)
        self.export_thread.start()

    def on_export_finished(self):
        QMessageBox.information(self, "Export Finished", "Video export completed!")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

Недостатком этого способа является то, что если у вас есть текстовая анимация в вашей сцене, вам придется научиться конвертировать ее в анимацию .ASS, что должно быть возможно, также если у вас есть нетекстовые анимации в вашей QGraphicsScene и вы хотите экспортировать их как MP4, вам, вероятно, придется использовать многопроцессорную обработку для захвата сцены (могут быть и другие способы). Обводка текста применима только в том случае, если вы можете ее получить. Я не уверен, есть ли способ использовать базовый QGraphicsTextItem, но поскольку я не использую систему контуров Qt, а вместо этого рисую свою собственную, я могу ее получить.

Еще немного информации о .ASS, которая мне показалась полезной, и о том, как я создаю контурный текст: https://stackoverflow.com/a/78362730/22802649https://hhsprings.bitbucket.io/docs/programming/examples/ffmpeg/subtitle/ass.html

Другие вопросы по теме