最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時(shí)間:2022年07月24日 08:16:07   作者:之一Yo  
最近無事看到了電腦桌面又想到了最近入門的pyqt5,所以下面這篇文章主要給大家介紹了關(guān)于如何使用pyqt實(shí)現(xiàn)桌面歌詞功能的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

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

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

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

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

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

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

# coding:utf-8
from PyQt5.QtCore import QPointF, QPropertyAnimation, Qt, pyqtProperty
from PyQt5.QtGui import (QColor, QFont, QFontMetrics, QPainter, QPainterPath,
                         QPen)
from PyQt5.QtWidgets import QWidget

config = {
    "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)

上述代碼對外提供了兩個(gè)接口 setLyric(lyric, duration, update) 和 setPlay(isPlay),用于更新歌詞和控制歌詞動(dòng)畫的開始與暫停。下面是一個(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", "測試桌面歌詞樣式"], 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)介紹完畢,完整的播放器界面代碼可參見:https://github.com/zhiyiYo/Groove,以上

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

相關(guān)文章

  • Python編程中閉包的變量作用域問題解析

    Python編程中閉包的變量作用域問題解析

    這篇文章主要介紹了Python編程中閉包的變量作用域問題解析,在學(xué)習(xí)Python的返回函數(shù)的時(shí)候,我發(fā)現(xiàn)里面涉及了幾個(gè)問題,在這里為大家分享講解下
    2021-10-10
  • 使用pandas中的DataFrame.rolling方法查看時(shí)間序列中的異常值

    使用pandas中的DataFrame.rolling方法查看時(shí)間序列中的異常值

    Pandas是Python中最受歡迎的數(shù)據(jù)分析和處理庫之一,提供了許多強(qiáng)大且靈活的數(shù)據(jù)操作工具,在Pandas中,DataFrame.rolling方法是一個(gè)強(qiáng)大的工具,在本文中,我們將深入探討DataFrame.rolling方法的各種參數(shù)和示例,以幫助您更好地理解和應(yīng)用這個(gè)功能
    2023-12-12
  • Python3利用SMTP協(xié)議發(fā)送E-mail電子郵件的方法

    Python3利用SMTP協(xié)議發(fā)送E-mail電子郵件的方法

    SMTP(Simple Mail Transfer Protocol)即簡單郵件傳輸協(xié)議,它是一組用于由源地址到目的地址傳送郵件的規(guī)則,由它來控制信件的中轉(zhuǎn)方式。下面這篇文章主要給大家介紹了關(guān)于Python3如何利用SMTP協(xié)議發(fā)送E-mail電子郵件的方法,需要的朋友可以參考下。
    2017-09-09
  • 如何爬取通過ajax加載數(shù)據(jù)的網(wǎng)站

    如何爬取通過ajax加載數(shù)據(jù)的網(wǎng)站

    這篇文章主要介紹了如何爬取通過ajax加載數(shù)據(jù)的網(wǎng)站,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-08-08
  • Python中parsel兩種獲取數(shù)據(jù)方式小結(jié)

    Python中parsel兩種獲取數(shù)據(jù)方式小結(jié)

    本文主要介紹了Python中parsel兩種獲取數(shù)據(jù)方式小結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • AI與Python人工智能啟發(fā)式搜索概念理解

    AI與Python人工智能啟發(fā)式搜索概念理解

    這篇文章主要為大家介紹了AI與Python啟發(fā)式搜索概念詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • Python實(shí)現(xiàn)智能貪吃蛇游戲的示例代碼

    Python實(shí)現(xiàn)智能貪吃蛇游戲的示例代碼

    我想大家都玩過諾基亞上面的貪吃蛇吧,這篇文章將帶你一步步用python語言實(shí)現(xiàn)一個(gè)snake小游戲,文中的示例代碼講解詳細(xì),感興趣的可以了解一下
    2022-07-07
  • python tkinter實(shí)現(xiàn)屏保程序

    python tkinter實(shí)現(xiàn)屏保程序

    這篇文章主要為大家詳細(xì)介紹了python tkinter實(shí)現(xiàn)屏保程序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • 使用Python實(shí)現(xiàn)檢測并刪除離群值

    使用Python實(shí)現(xiàn)檢測并刪除離群值

    離群值是一個(gè)數(shù)據(jù)項(xiàng)/對象,它明顯偏離其余的(所謂的正常)對象,本文主要為大家詳細(xì)介紹了如何使用Python實(shí)現(xiàn)檢測并刪除離群值,感興趣的小伙伴可以了解下
    2024-01-01
  • python爬蟲中抓取指數(shù)的實(shí)例講解

    python爬蟲中抓取指數(shù)的實(shí)例講解

    在本篇文章里小編給大家整理了關(guān)于python爬蟲中抓取指數(shù)的實(shí)例講解內(nèi)容,有興趣的朋友們學(xué)習(xí)下。
    2020-12-12

最新評論

长春市| 固镇县| 门源| 象州县| 杭州市| 阿拉善右旗| 中西区| 绵竹市| 广元市| 铜鼓县| 延川县| 尚义县| 吴旗县| 邢台县| 新巴尔虎右旗| 宣威市| 琼中| 富顺县| 岳池县| 濮阳市| 惠来县| 民勤县| 仙游县| 育儿| 昂仁县| 城固县| 日喀则市| 富平县| 尖扎县| 石屏县| 连江县| 洞口县| 海淀区| 乐平市| 康定县| 绥德县| 太仓市| 邻水| 托克逊县| 舒城县| 淮阳县|