How to realize the smooth rolling subtitle perfectly in pyqt

Visual effects of scrolling subtitles

Due to the need of the project, I plan to make a horizontal rolling caption. A search on the Internet will harm... Another group of people copy them and return them one card one card according to their method, because they are all realized by calculating the subscript of characters. It's up to you in the end. The main idea is to achieve the effect of blinding eyes rolling by timer timing refresh + drawing two complete strings. The specific effect is shown in the following figure (recorded frame rate is 60fps):

Specific implementation mode

  1. Create a windowscrolltextwindow to display the song name and singer name on the window. The width of song name and singer name string is calculated by QFontMetrics, the largest one is selected, and then compared with the maximum window width to set the window width and decide whether to enable scrolling effect. A spacing property is set in the constructor to separate two identical strings. Two flag bits, isSongNameAllOut and isSongerNameAllOut, are also set to mark whether all strings have been moved out of the window. These two flag bits are very important. If the flag bits are not fixed, the string position will jump. As mentioned earlier, the scrolling effect is achieved by drawing two identical strings. Therefore, the abscissa of the first character of the two strings is very important. The abscissa of the two strings is determined by setting the flag bits isXXXAllOut, spacing, the overflow times of the timer, currentIndex, and the moving step length of the string moveStep. The specific code is as follows:
import sys

from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QFont, QFontMetrics, QPainter,QPixmap
from PyQt5.QtWidgets import QApplication, QLabel, QWidget


class ScrollTextWindow(QWidget):
    """ Rolling captions """

    def __init__(self, songName, songerName, parent=None):
        super().__init__(parent)
        self.songName = songName
        self.songerName = songerName
        # Instantiation timer
        self.timer = QTimer(self)
        # Set refresh time and move distance
        self.timeStep = 20
        self.moveStep = 1
        self.songCurrentIndex = 0
        self.songerCurrentIndex = 0
        # Set string overflow flag bit
        self.isSongNameAllOut = False
        self.isSongerNameAllOut = False
        # Sets the width of white space between two strings
        self.spacing = 25
        # Initialization interface
        self.initWidget()

    def initWidget(self):
        """ Initialization interface """
        self.setFixedHeight(115)
        self.setAttribute(Qt.WA_StyledBackground)
        # Adjust window width
        self.adjustWindowWidth()
        # Initialize timer
        self.timer.setInterval(self.timeStep)
        self.timer.timeout.connect(self.updateIndex)
        # Turn on scrolling whenever a string width is greater than the window width:
        if self.isSongerNameTooLong or self.isSongNameTooLong:
            self.timer.start()

    def getTextWidth(self):
        """ Calculates the total width of the text """
        songFontMetrics = QFontMetrics(QFont('Microsoft YaHei', 14, 400))
        self.songNameWidth = sum([songFontMetrics.width(i)
                                  for i in self.songName])
        songerFontMetrics = QFontMetrics(QFont('Microsoft YaHei', 12, 500))
        self.songerNameWidth = sum(
            [songerFontMetrics.width(i) for i in self.songerName])

    def adjustWindowWidth(self):
        """ Adjust window width according to string length """
        self.getTextWidth()
        maxWidth = max(self.songNameWidth, self.songerNameWidth)
        # Determine whether the width of a string exceeds the maximum width of the window
        self.isSongNameTooLong = self.songNameWidth > 250
        self.isSongerNameTooLong = self.songerNameWidth > 250
        # Set the width of the window
        self.setFixedWidth(min(maxWidth, 250))

    def updateIndex(self):
        """ Update subscript """
        self.update()
        self.songCurrentIndex += 1
        self.songerCurrentIndex += 1
        # Set subscript reset condition
        resetSongIndexCond = self.songCurrentIndex * \
            self.moveStep >= self.songNameWidth + self.spacing * self.isSongNameAllOut
        resetSongerIndexCond = self.songerCurrentIndex * \
            self.moveStep >= self.songerNameWidth + self.spacing * self.isSongerNameAllOut
        # As long as the conditions are met, the subscript should be reset and the string overflow should be set to ensure that the string overflow will not jump because of the blank space left
        if resetSongIndexCond:
            self.songCurrentIndex = 0
            self.isSongNameAllOut = True
        if resetSongerIndexCond:
            self.songerCurrentIndex = 0
            self.isSongerNameAllOut = True

    def paintEvent(self, e):
        """ Draw text """
        # super().paintEvent(e)
        painter = QPainter(self)
        painter.setPen(Qt.white)
        # Draw song name
        painter.setFont(QFont('Microsoft YaHei', 14))
        if self.isSongNameTooLong:
            # Two complete strings are actually drawn
            # Draw the first string from the negative abscissa
            painter.drawText(self.spacing * self.isSongNameAllOut - self.moveStep *
                             self.songCurrentIndex, 54, self.songName)
            # Draws the second string
            painter.drawText(self.songNameWidth - self.moveStep * self.songCurrentIndex +
                             self.spacing * (1 + self.isSongNameAllOut), 54, self.songName)
        else:
            painter.drawText(0, 54, self.songName)

        # Draw artist name  
        painter.setFont(QFont('Microsoft YaHei', 12, 500))
        if self.isSongerNameTooLong:
            painter.drawText(self.spacing * self.isSongerNameAllOut - self.moveStep *
                             self.songerCurrentIndex, 82, self.songerName)
            painter.drawText(self.songerNameWidth - self.moveStep * self.songerCurrentIndex +
                             self.spacing * (1 + self.isSongerNameAllOut), 82, self.songerName)
        else:
            painter.drawText(0, 82, self.songerName)

  1. Then create the parent window SongInfoCard to display album cover and scrolling Subtitles:
class SongInfoCard(QWidget):
    """ Music card information bar on the left """

    def __init__(self, songInfo: dict, parent=None):
        super().__init__(parent)
        # Save information
        self.songInfo = songInfo
        self.songName = self.songInfo['songName']
        self.songerName = self.songInfo['songer']
        # Instantiation widget
        self.albumPic = QLabel(self)
        self.scrollTextWindow = ScrollTextWindow(
            self.songName, self.songerName, self)
        # Initialization interface
        self.initWidget()

    def initWidget(self):
        """ Initialize widget """
        self.setFixedHeight(115)
        self.setFixedWidth(115 + 15 + self.scrollTextWindow.width() + 25)
        self.setAttribute(Qt.WA_StyledBackground)
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.scrollTextWindow.move(130, 0)
        self.albumPic.setPixmap(QPixmap(self.songInfo['album'][-1]).scaled(
                                115, 115, Qt.KeepAspectRatio, Qt.SmoothTransformation))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    songInfo = {
        'songName': 'ハッピーでバッドなSleepりはshallowい', 'songer': 'Lock that',
        'album': [r'resource\Album Cover\ハッピーでバッドなSleepりはshallowい\ハッピーでバッドなSleepりはshallowい.png']}
    demo = SongInfoCard(songInfo)
    demo.setStyleSheet('background:rgb(129,133,137)')
    demo.show()
    sys.exit(app.exec_())

epilogue

At this point, the perfect realization of the silky rolling effect, if you have help, please point a "like" (๑◡๑๑๑). Make complaints about what smatter is. If there are not so many copies to copy, people who do not know to write will write blogs. I may not be so tired.

Tags: Qt

Posted on Mon, 29 Jun 2020 04:09:51 -0400 by freshrod