Analysis and solution of PyQt5 playing video with transparent channel

Analysis and solutions on using PyQt5 to play videos with transparent channels:

Recently, when I was practicing on the PyQt5 project, I encountered the problem of playing a transparent channel background video. After searching for a long time on the Internet to no avail, I finally solved the problem by myself. The solution was quite tricky. But it would be nice to achieve the final effect

PyQt5 plays video with transparent channel

Some images have a transparency channel, so the background of these images can be set to be transparent. In the same way, videos can also have transparent channels, which means that the background of the video can also be set to be transparent, but how to use the playback module of PyQt5 to play videos with transparent backgrounds has become a problem. The effect I want to achieve is similar to a function of the current mainstream editing software: the video consists of two parts: the foreground and the background. The foreground is a portrait transparent background video, and the background is a background image. The foreground video can be dragged on the background image arbitrarily. The animation is similar to the function of arbitrarily changing the position of the character in the background.

Cause analysis:

PyQt5 itself does not directly support playing videos with transparency channels. Its video playback function is mainly implemented through PyQt5’s QMediaPlayer and QVideoWidget classes. These classes use Qt’s Multimedia module to process videos, but this module does not support transparent channels.

Solution:

Conjectures and Assumptions

Because I want to solve the problem most directly, try to avoid using other video playback libraries or more professional media processing libraries. Although pyqt5 cannot directly play videos with transparent backgrounds, it can play pictures with transparent backgrounds, and then I have a bold idea. My guess: Split the video with a transparent background into frame pictures with a transparent background one by one, and then use PyQt5 to play the frame pictures in sequence and play them according to the original number of frames of the video. Is this possible? What about the function of playing transparent channel videos that I mentioned?

Assumption is established, start practicing

1. Import the required libraries and modules

First, we need to import some necessary libraries and modules:

from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import Qt, QPoint, QDirIterator, QTimer
from PyQt5.QtGui import QPixmap
from VideoPlayWindow import Ui_VideoPlayWindow
from PyQt5.QtGui import QPalette, QBrush

2. Create the main window class

Next, we define a class called VideoPlayWindow, which inherits from QtWidgets.QMainWindow and is the main window of our application:

class VideoPlayWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None, image_folder="E:/AIVideo/ceshi", time_interval=40):
        super(VideoPlayWindow, self).__init__(parent)
        self.ui = Ui_VideoPlayWindow()
        self.ui.setupUi(self)

In the constructor of this class, we first called the constructor of the parent class to create a window, then created an instance of Ui_VideoPlayWindow, and called its setupUi method to set the UI of the window.

3. Setup UI

Next, we set the background image of the window and the background color of the frame:

 # Set the background image of centralwidget
        palette = QPalette()
        pixmap = QPixmap("E:/AIVideo/bg.jpg")
        palette.setBrush(QPalette.Background, QBrush(pixmap))
        self.ui.centralwidget.setPalette(palette)
        self.ui.centralwidget.setAutoFillBackground(True)

        #Set the background of the frame to transparent
        self.ui.frame.setStyleSheet("background-color: transparent;")

We created an instance of QPalette, loaded a background image using QPixmap, and then set this image as the background of the centralwidget. We also set the background color of the frame to transparent.

4. Initialize variables and start carousel

At the end of the constructor, we initialize some variables and start the image carousel:

 QtCore.QTimer.singleShot(0, self.adjustLabelSizeAndPosition)
        self.dragging = False
        self.playing = False
        self.image_folder = image_folder
        self.time_interval = time_interval

        #Set label style
        self.ui.label.setStyleSheet("background-color: transparent; border: 1px solid black;")

        self.start_slideshow() # Start playing pictures

Here, we use QTimer’s singleShot method to call the adjustLabelSizeAndPosition method, which will be introduced later. We also set some flag variables, such as dragging and playing, and some configuration parameters, such as image_folder and time_interval. Finally, we set the label style and called the start_slideshow method to start the image carousel.

5. Handle window size change events

In the VideoPlayWindow class, we also define a resizeEvent method to handle window size change events

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

When the size of the window changes, this method will be called automatically. We call the adjustLabelSizeAndPosition method in this method to adjust the size and position of the label.

6. Adjust the size and position of the label

The code for the adjustLabelSizeAndPosition method is as follows:

 def adjustLabelSizeAndPosition(self):
        width = int(self.ui.frame.width() / 3)
        height = int(self.ui.frame.height() * 2 / 3)
        self.ui.label.setFixedSize(width, height)
        self.ui.label.move(int((self.ui.frame.width() - width) / 2), int((self.ui.frame.height() - height) / 2))

This method first calculates the new width and height of the label, then sets the label’s size and moves the label to the center of the frame.

7. Handling mouse events

In the VideoPlayWindow class, we also define three methods to handle mouse events:

 def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton and self.ui.label.geometry().contains(event.pos()):
            self.dragging = True
            self.dragPosition = event.pos() - self.ui.label.pos()
            event.accept()

    def mouseMoveEvent(self, event):
        if event.buttons() == Qt.LeftButton and self.dragging:
            new_pos = event.pos() - self.dragPosition
            new_pos.setX(max(0, min(new_pos.x(), self.ui.frame.width() - self.ui.label.width())))
            new_pos.setY(max(0, min(new_pos.y(), self.ui.frame.height() - self.ui.label.height())))
            self.ui.label.move(new_pos)
            event.accept()

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.dragging = False

These three methods handle mouse press, move and release events respectively. When the left mouse button is pressed on the label, we set dragging to True and save the current mouse position. When the mouse moves, if dragging is True, we calculate the new label position and move the label to this position. When the left mouse button is released, we set dragging to False.

8. Handling keyboard events

In the VideoPlayWindow class, we also define a keyPressEvent method to handle keyboard events:

 def keyPressEvent(self, event):
        if event.key() == Qt.Key_Space:
            if self.playing:
                self.timer.stop()
            else:
                self.start_slideshow()
                self.timer.start(self.time_interval)
            self.playing = not self.playing

This method is called when the user presses the space bar. If the picture is currently playing, we stop the timer; if there is no picture currently playing, we start the picture carousel and start the timer.

9. Start image carousel

The code of the start_slideshow method is as follows:

 def start_slideshow(self):
        self.images_iter = QDirIterator(self.image_folder, ["*.png", "*.jpg"])
        self.timer = QTimer()
        self.timer.timeout.connect(self.next_image)
        self.timer.start(self.time_interval) # Start the timer immediately
        self.playing = True # Set playing to True

This method first creates an instance of QDirIterator to traverse the image files in the specified folder, then creates an instance of QTimer and connects its timeout signal to the next_image method. Then we started the timer and set playing to True.

10. Switch to the next picture

The code of next_image method is as follows:

 def next_image(self):
        if self.images_iter.hasNext():
            image_file = self.images_iter.next()
            pixmap = QPixmap(image_file)
            self.ui.label.setPixmap(pixmap.scaled(self.ui.label.size(), Qt.KeepAspectRatio))

This method first checks whether there is a next picture. If so, it loads the next picture and sets it on the label.

11. Run the application

Finally, we create an instance of VideoPlayWindow in the main program and display the window:

if __name__ == "__main__":
    importsys
    app = QtWidgets.QApplication(sys.argv)
    window = VideoPlayWindow()
    window.show()
    sys.exit(app.exec_())

This is all the code for our image carousel application. Although this application is simple, it shows how to use PyQt5 to create a graphical user interface, handle various user events, and use timers to achieve dynamic effects. Hope this article can help you better understand and use PyQt5.

Final effect

The effect can meet my basic needs. If there is a better solution, please point it out.