我的编程空间,编程开发者的网络收藏夹
学习永远不晚

教你使用pyqt实现桌面歌词功能

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

教你使用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-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)

上述代码对外提供了两个接口 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实现桌面歌词内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

教你使用pyqt实现桌面歌词功能

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Win10使用多桌面功能开启多个虚拟桌面教程

很多微软的用户对苹果电脑的多桌面功能很羡慕,不过现在也不用羡慕了。因http://www.cppcns.com为Win10系统就增加了多桌面的功能,那么Win10多桌面功能如何使用呢?本文就来介绍一下Win10使用多桌面功能的步骤。步骤1在
2023-06-16

手把手教你使用redis实现排行榜功能

目录一、需求背景二、实现思路 1、利用数据库2、利用Redis总结一、需求背景最近项目需要做排行榜功能,实现员工邀请用户注册排行榜,要求是实时更新,查询要快。员工所属支行、二级行、省行,界面要根据条件显示排名数据。效果如下图所示:原型图
2023-04-14

使用Go语言实现谷歌翻译功能

本指南详细介绍了如何使用Go语言集成Google翻译API,实现文本翻译和语言识别功能。通过安装依赖项、创建服务账户、配置客户端,可以轻松翻译文本,并识别输入文本的语言。文中提供了示例代码,展示了如何使用这些功能翻译文本并检测语言,帮助开发人员轻松实现翻译功能。
使用Go语言实现谷歌翻译功能
2024-04-02

使用Python怎么实现一个词云功能

使用Python怎么实现一个词云功能?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。Python的优点有哪些1、简单易用,与C/C++、Java、C# 等传统语言
2023-06-14

手把手教你使用Vue实现一个tab栏切换功能

随着前端技术的不断发展,Vue已成为最受欢迎的JavaScript框架之一。Vue的出现,让前端开发变得更加简单和高效。在Vue中,我们可以通过指令和组件来构建一个复杂的界面。其中,tab栏经常出现在网站的导航栏或者选项卡中,本文将介绍如何使用Vue实现tab栏切换。一、创建Vue项目首先需要安装Vue CLI,执行如下命令:```npm install -g vue-cli`
2023-05-14

怎么在c#中利用Win32Api实现返回Windows桌面功能

怎么在c#中利用Win32Api实现返回Windows桌面功能?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。实现方法Windows回到桌面功能的实现方式有多种,可以模拟快捷
2023-06-15

教你用go语言实现比特币交易功能(Transaction)

比特币交易 交易(transaction)是比特币的核心所在,而区块链唯一的目的,也正是为了能够安全可靠地存储交易。在区块链中,交易一旦被创建,就没有任何人能够再去修改或是删除它。 对于每一笔新的交易,它的输入会引用(reference)
2022-06-07

手把手教你使用Vue3实现一个飘逸元素拖拽功能

怎么实现元素拖拽功能?下面本篇文章一步步带大家了解如何使用Vue3实现一个飘逸元素拖拽功能,并在实例中了解相关知识点,希望对大家有所帮助!
2023-05-14

如何使用PHP实现页面返回功能

标题:PHP实现页面返回功能的方法及代码示例在Web开发中,经常会遇到需要实现页面返回功能的情况,也就是用户点击返回按钮时能够返回到上一个页面。在PHP中,通过使用header函数结合JavaScript,可以实现这一功能。本文将介绍如何
如何使用PHP实现页面返回功能
2024-03-08

大神教你用Python实现Wake On Lan远程开机功能

这篇文章主要介绍了使用Python实现Wake On Lan远程开机功能,文中给大家补充介绍了python通过wakeonlan唤醒内网电脑开机,非常不错,感兴趣的朋友跟随小编一起学习吧Wake-On-LAN简称WOL,是一种电源管理功能;
2023-06-02

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录