教你使用pyqt實現桌面歌詞功能

    目錄

    前言

    酷狗、網抑云和 QQ 音樂都有桌面歌詞功能,這篇博客也將使用 pyqt 實現桌面歌詞功能,效果如下圖所示:

    代碼實現

    桌面歌詞部件 LyricWidget 在 paintEvent 中繪制歌詞。我們可以直接使用 QPainter.drawText 來繪制文本,但是通過這種方式無法對歌詞進行描邊。所以這里更換為 QPainterPath 來實現,使用 QPainterPath.addText 將歌詞添加到繪制路徑中,接著使用 Qainter.strokePath 進行描邊,Qainter.fillPath 繪制歌詞,這里得繪制順序不能調換。

    對于歌詞得高亮部分需要特殊處理,假設當前高亮部分得寬度為 w,我們需要對先前繪制歌詞得 QPainterPath 進行裁剪,只留下寬度為 w 得部分,此處通過 QPainterPath.intersected 計算與寬度為 w 得矩形路徑得交集來實現裁剪。

    對于高亮部分得動畫,我們既可以使用傳統得 QTimer,也可以使用封裝地更加徹底得 QPropertyAnimation 來實現(本文使用后者)。這里需要進行動畫展示得是高亮部分,也就是說我們只需改變“高亮寬度”這個屬性即可。PyQt 為我們提供了 pyqtProperty,類似于 python 自帶得 property,使用 pyqtProperty 可以給部件注冊一個屬性,該屬性可以搭配動畫來食用。

    除了高亮動畫外,我們還在 LyricWidget 中注冊了滾動動畫,用于處理歌詞長度大于視口寬度得情況。

    # coding:utf-8from PyQt5.QtCore import QPointF, QPropertyAnimation, Qt, pyqtPropertyfrom PyQt5.QtGui import (QColor, QFont, QFontMetrics, QPainter, QPainterPath,                         QPen)from PyQt5.QtWidgets import QWidgetconfig = {    "lyric.font-color": [255, 255, 255],    "lyric.highlight-color": [0, 153, 188],    "lyric.font-size": 50,    "lyric.stroke-size": 5,    "lyric.stroke-color": [0, 0, 0],    "lyric.font-family": "Microsoft YaHei",    "lyric.alignment": "Center"}class LyricWidget(QWidget):    """ Lyric widget """    def __init__(self, parent=None):        super().__init__(parent=parent)        self.setAttribute(Qt.WA_TranslucentBackground)        self.lyric = []        self.duration = 0        self.__originMaskWidth = 0        self.__translationMaskWidth = 0        self.__originTextX = 0        self.__translationTextX = 0        self.originMaskWidthAni = QPropertyAnimation(            self, b'originMaskWidth', self)        self.translationMaskWidthAni = QPropertyAnimation(            self, b'translationMaskWidth', self)        self.originTextXAni = QPropertyAnimation(            self, b'originTextX', self)        self.translationTextXAni = QPropertyAnimation(            self, b'translationTextX', self)    def paintEvent(self, e):        if not self.lyric:            return        painter = QPainter(self)        painter.setRenderHints(            QPainter.Antialiasing | QPainter.TextAntialiasing)        # draw original lyric        self.__drawLyric(            painter,            self.originTextX,            config["lyric.font-size"],            self.originMaskWidth,            self.originFont,            self.lyric[0]        )        if not self.hasTranslation():            return        # draw translation lyric        self.__drawLyric(            painter,            self.translationTextX,            25 + config["lyric.font-size"]*5/3,            self.translationMaskWidth,            self.translationFont,            self.lyric[1]        )    def __drawLyric(self, painter: QPainter, x, y, width, font: QFont, text: str):        """ draw lyric """        painter.setFont(font)        # draw background text        path = QPainterPath()        path.addText(QPointF(x, y), font, text)        painter.strokePath(path, QPen(            QColor(*config["lyric.stroke-color"]), config["lyric.stroke-size"]))        painter.fillPath(path, QColor(*config['lyric.font-color']))        # draw foreground text        painter.fillPath(            self.__getMaskedLyricPath(path, width),            QColor(*config['lyric.highlight-color'])        )    def __getMaskedLyricPath(self, path: QPainterPath, width: float):        """ get the masked lyric path """        subPath = QPainterPath()        rect = path.boundingRect()        rect.setWidth(width)        subPath.addRect(rect)        return path.intersected(subPath)    def setLyric(self, lyric: list, duration: int, update=False):        """ set lyric        Parameters        ----------        lyric: list            list contains original lyric and translation lyric        duration: int            lyric duration in milliseconds        update: bool            update immediately or not        """        self.lyric = lyric or [""]        self.duration = max(duration, 1)        self.__originMaskWidth = 0        self.__translationMaskWidth = 0        # stop running animations        for ani in self.findChildren(QPropertyAnimation):            if ani.state() == ani.Running:                ani.stop()        # start scroll animation if text is too long        fontMetrics = QFontMetrics(self.originFont)        w = fontMetrics.width(lyric[0])        if w > self.width():            x = self.width() - w            self.__setAnimation(self.originTextXAni, 0, x)        else:            self.__originTextX = self.__getLyricX(w)            self.originTextXAni.setEndValue(None)        # start foreground color animation        self.__setAnimation(self.originMaskWidthAni, 0, w)        if self.hasTranslation():            fontMetrics = QFontMetrics(self.translationFont)            w = fontMetrics.width(lyric[1])            if w > self.width():                x = self.width() - w                self.__setAnimation(self.translationTextXAni, 0, x)            else:                self.__translationTextX = self.__getLyricX(w)                self.translationTextXAni.setEndValue(None)            self.__setAnimation(self.translationMaskWidthAni, 0, w)        if update:            self.update()    def __getLyricX(self, w: float):        """ get the x coordinate of lyric """        alignment = config["lyric.alignment"]        if alignment == "Right":            return self.width() - w        elif alignment == "Left":            return 0        return self.width()/2 - w/2    def getOriginMaskWidth(self):        return self.__originMaskWidth    def getTranslationMaskWidth(self):        return self.__translationMaskWidth    def getOriginTextX(self):        return self.__originTextX    def getTranslationTextX(self):        return self.__translationTextX    def setOriginMaskWidth(self, pos: int):        self.__originMaskWidth = pos        self.update()    def setTranslationMaskWidth(self, pos: int):        self.__translationMaskWidth = pos        self.update()    def setOriginTextX(self, pos: int):        self.__originTextX = pos        self.update()    def setTranslationTextX(self, pos):        self.__translationTextX = pos        self.update()    def __setAnimation(self, ani: QPropertyAnimation, start, end):        if ani.state() == ani.Running:            ani.stop()        ani.setStartValue(start)        ani.setEndValue(end)        ani.setDuration(self.duration)    def setPlay(self, isPlay: bool):        """ set the play status of lyric """        for ani in self.findChildren(QPropertyAnimation):            if isPlay and ani.state() != ani.Running and ani.endValue() is not None:                ani.start()            elif not isPlay and ani.state() == ani.Running:                ani.pause()    def hasTranslation(self):        return len(self.lyric) == 2    def minimumHeight(self) -> int:        size = config["lyric.font-size"]        h = size/1.5+60 if self.hasTranslation() else 40        return int(size+h)    @property    def originFont(self):        font = QFont(config["lyric.font-family"])        font.setPixelSize(config["lyric.font-size"])        return font    @property    def translationFont(self):        font = QFont(config["lyric.font-family"])        font.setPixelSize(config["lyric.font-size"]//1.5)        return font    originMaskWidth = pyqtProperty(        float, getOriginMaskWidth, setOriginMaskWidth)    translationMaskWidth = pyqtProperty(        float, getTranslationMaskWidth, setTranslationMaskWidth)    originTextX = pyqtProperty(float, getOriginTextX, setOriginTextX)    translationTextX = pyqtProperty(        float, getTranslationTextX, setTranslationTextX)

    上述代碼對外提供了兩個接口 setLyric(lyric, duration, update) 和 setPlay(isPlay),用于更新歌詞和控制歌詞動畫得開始與暫停。下面是一個最小使用示例,里面使用 Qt.SubWindow 標志使得桌面歌詞可以在主界面最小化后仍然顯示在桌面上,同時不會多出一個應用圖標(Windows 是這樣,Linux 不一定):

    class Demo(QWidget):    def __init__(self):        super().__init__(parent=None)        # 創建桌面歌詞        self.desktopLyric = QWidget()        self.lyricWidget = LyricWidget(self.desktopLyric)        self.desktopLyric.setAttribute(Qt.WA_TranslucentBackground)        self.desktopLyric.setWindowFlags(            Qt.FramelessWindowHint | Qt.SubWindow | Qt.WindowStaysOnTopHint)        self.desktopLyric.resize(800, 300)        self.lyricWidget.resize(800, 300)                # 必須有這一行才能顯示桌面歌詞界面        self.desktopLyric.show()        # 設置歌詞        self.lyricWidget.setLyric(["Test desktop lyric style", "測試桌面歌詞樣式"], 3000)        self.lyricWidget.setPlay(True)if __name__ == '__main__':    app = QApplication(sys.argv)    w = Demo()    w.show()    app.exec_()

    后記

    至此關于桌面歌詞得實現方案已經介紹完畢,完整得播放器界面代碼可參見:https://github.com/zhiyiYo/Groove,以上

    到此這篇關于教你使用pyqt實現桌面歌詞功能得內容就介紹到這了,更多相關pyqt實現桌面歌詞內容請搜索之家以前得內容或繼續瀏覽下面得相關內容希望大家以后多多支持之家!

    聲明:所有內容來自互聯網搜索結果,不保證100%準確性,僅供參考。如若本站內容侵犯了原著者的合法權益,可聯系我們進行處理。
    發表評論
    更多 網友評論1 條評論)
    暫無評論

    返回頂部

    主站蜘蛛池模板: 日韩在线不卡免费视频一区| 东京热无码av一区二区| 少妇人妻精品一区二区三区| 精品国产日韩亚洲一区| 日本一区二区三区免费高清在线 | 文中字幕一区二区三区视频播放 | 国产福利一区视频| 日本在线电影一区二区三区| 国产一区二区精品久久91| 国产乱码精品一区二区三| 高清国产AV一区二区三区| 最新欧美精品一区二区三区| 久久毛片一区二区| 青娱乐国产官网极品一区| 久久久久成人精品一区二区| 中文字幕AV一区二区三区 | 国产成人精品a视频一区| 波多野结衣AV无码久久一区 | 国产品无码一区二区三区在线| 一区二区三区无码高清| 国产免费一区二区视频| 天天视频一区二区三区| 亚洲熟妇av一区二区三区漫画| 亚洲一区二区三区播放在线| 一区二区精品视频| 伊人久久精品无码麻豆一区| chinese国产一区二区| 中文字幕无线码一区2020青青| 久久久久人妻一区精品果冻| 中文字幕人妻第一区| 嫩B人妻精品一区二区三区| av一区二区三区人妻少妇| 亚洲AV无码一区东京热| 视频一区二区精品的福利| 国产香蕉一区二区精品视频| 亚洲AⅤ无码一区二区三区在线| 亚洲国产精品一区二区久| 中文字幕在线观看一区二区 | 亚洲一区二区三区高清| 国产一区二区三区樱花动漫| 无码国产精成人午夜视频一区二区|