教你使用pyqt實(shí)現(xiàn)桌面歌詞功能

    目錄

    前言

    酷狗、網(wǎng)抑云和 QQ 音樂(lè)都有桌面歌詞功能,這篇博客也將使用 pyqt 實(shí)現(xiàn)桌面歌詞功能,效果如下圖所示:

    代碼實(shí)現(xiàn)

    桌面歌詞部件 LyricWidget 在 paintEvent 中繪制歌詞。我們可以直接使用 QPainter.drawText 來(lái)繪制文本,但是通過(guò)這種方式無(wú)法對(duì)歌詞進(jìn)行描邊。所以這里更換為 QPainterPath 來(lái)實(shí)現(xiàn),使用 QPainterPath.addText 將歌詞添加到繪制路徑中,接著使用 Qainter.strokePath 進(jìn)行描邊,Qainter.fillPath 繪制歌詞,這里得繪制順序不能調(diào)換。

    對(duì)于歌詞得高亮部分需要特殊處理,假設(shè)當(dāng)前高亮部分得寬度為 w,我們需要對(duì)先前繪制歌詞得 QPainterPath 進(jìn)行裁剪,只留下寬度為 w 得部分,此處通過(guò) QPainterPath.intersected 計(jì)算與寬度為 w 得矩形路徑得交集來(lái)實(shí)現(xiàn)裁剪。

    對(duì)于高亮部分得動(dòng)畫(huà),我們既可以使用傳統(tǒng)得 QTimer,也可以使用封裝地更加徹底得 QPropertyAnimation 來(lái)實(shí)現(xiàn)(本文使用后者)。這里需要進(jìn)行動(dòng)畫(huà)展示得是高亮部分,也就是說(shuō)我們只需改變“高亮寬度”這個(gè)屬性即可。PyQt 為我們提供了 pyqtProperty,類似于 python 自帶得 property,使用 pyqtProperty 可以給部件注冊(cè)一個(gè)屬性,該屬性可以搭配動(dòng)畫(huà)來(lái)食用。

    除了高亮動(dòng)畫(huà)外,我們還在 LyricWidget 中注冊(cè)了滾動(dòng)動(dòng)畫(huà),用于處理歌詞長(zhǎng)度大于視口寬度得情況。

    # 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)

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

    class Demo(QWidget):    def __init__(self):        super().__init__(parent=None)        # 創(chuàng)建桌面歌詞        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()        # 設(shè)置歌詞        self.lyricWidget.setLyric(["Test desktop lyric style", "測(cè)試桌面歌詞樣式"], 3000)        self.lyricWidget.setPlay(True)if __name__ == '__main__':    app = QApplication(sys.argv)    w = Demo()    w.show()    app.exec_()

    后記

    至此關(guān)于桌面歌詞得實(shí)現(xiàn)方案已經(jīng)介紹完畢,完整得播放器界面代碼可參見(jiàn):https://github.com/zhiyiYo/Groove,以上

    到此這篇關(guān)于教你使用pyqt實(shí)現(xiàn)桌面歌詞功能得內(nèi)容就介紹到這了,更多相關(guān)pyqt實(shí)現(xiàn)桌面歌詞內(nèi)容請(qǐng)搜索之家以前得內(nèi)容或繼續(xù)瀏覽下面得相關(guān)內(nèi)容希望大家以后多多支持之家!

    聲明:所有內(nèi)容來(lái)自互聯(lián)網(wǎng)搜索結(jié)果,不保證100%準(zhǔn)確性,僅供參考。如若本站內(nèi)容侵犯了原著者的合法權(quán)益,可聯(lián)系我們進(jìn)行處理。
    發(fā)表評(píng)論
    更多 網(wǎng)友評(píng)論1 條評(píng)論)
    暫無(wú)評(píng)論

    返回頂部

    主站蜘蛛池模板: 免费无码AV一区二区| 成人无码一区二区三区| 国产午夜三级一区二区三| 人妻夜夜爽天天爽爽一区| 亚洲欧美日韩国产精品一区| 成人影片一区免费观看| 久久蜜桃精品一区二区三区| 日本免费电影一区二区| 日韩一区二区三区视频| 在线精品一区二区三区| 国产探花在线精品一区二区| 国产乱子伦一区二区三区| 国产成人精品第一区二区| 日韩一区二区久久久久久| 国产高清在线精品一区小说 | 夜色阁亚洲一区二区三区| 无码国产精品一区二区免费 | 国产精品成人一区无码| 97av麻豆蜜桃一区二区| 精品日产一区二区三区手机| 国产福利电影一区二区三区久久久久成人精品综合 | 亚洲大尺度无码无码专线一区| 中文字幕一区二区三区在线不卡| 国语对白一区二区三区| 精产国品一区二区三产区| 亚洲一区动漫卡通在线播放| 亚洲AV美女一区二区三区| 日本道免费精品一区二区| 亚洲一区二区三区乱码A| 日本免费精品一区二区三区| 精品国产一区二区麻豆| 日韩美女在线观看一区| 亚洲AV日韩AV一区二区三曲| 亚洲一区二区三区成人网站| 一区二区三区高清| 无码人妻一区二区三区兔费| 亚洲丶国产丶欧美一区二区三区 | 一区二区乱子伦在线播放| 少妇人妻偷人精品一区二区| 精品伦精品一区二区三区视频 | 色综合视频一区二区三区|