教你使用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实现桌面歌词内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • python+PyQT实现系统桌面时钟

    用Python + PyQT写的一个系统桌面时钟,刚学习Python,写的比较简陋,但是基本的功能还可以. 功能: ①窗体在应用程序最上层,不用但是打开其他应用后看不到时间 ②左键双击全屏,可以做小屏保使用,再次双击退出全屏. ③系统托盘图标,主要参考PyQt4源码目录中的PyQt4\examples\desktop\systray下的程序 ④鼠标右键,将程序最小化 使用时需要heart.svg放在源代码同级目录下,[文件可在PyQt4示例代码目录下PyQt4\examples\desktop\

  • 基于PyQT5制作一个桌面摸鱼工具

    目录 前言 按键功能控制 主要功能 核心代码 前言 现在我能一整天都严肃地盯着屏幕,看起来就像在很认真地工作, 利用摸鱼,打开小说,可实行完美摸鱼,实时保存进度 用PYQT5 Mock一个摸鱼软件 类似于Thief 按键功能控制 q 退出 B 书签功能 F 增加字体大小 Shift F 减小字体 O 打开文件,现在仅仅支持 utf8格式的txt文件 主要功能 FlameLess Window 无边框窗口 一键快速退出 ini 文件读写 右键上下文菜单 核心代码 pyqt 实现功能还是比较顺畅的,

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

    目录 前言 代码实现 后记 前言 酷狗.网抑云和 QQ 音乐都有桌面歌词功能,这篇博客也将使用 pyqt 实现桌面歌词功能,效果如下图所示: 代码实现 桌面歌词部件 LyricWidget 在 paintEvent 中绘制歌词.我们可以直接使用 QPainter.drawText 来绘制文本,但是通过这种方式无法对歌词进行描边.所以这里更换为 QPainterPath 来实现,使用 QPainterPath.addText 将歌词添加到绘制路径中,接着使用 Qainter.strokePath 

  • Java模拟QQ桌面截图功能实现方法

    本文实例讲述了Java模拟QQ桌面截图功能实现方法.分享给大家供大家参考.具体如下: QQ的桌面截图功能非常方便,去年曾用Java模拟过一个,现整理出来. 本方法首先需要抓到屏幕的整个图象,将图象显示在一个JFrame中,再将JFrame全屏显示,这样就模拟出了一个桌面,Java也就可以获得鼠标的作用区域从而实现桌面中的小范围截屏. import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import

  • Java swing实现音乐播放器桌面歌词字体变色效果

    最近看到某音乐播放器的桌面歌词如下图 其中字母"U"有两种颜色,突发奇想想模仿一下实现文字变色. 思路:使用两种颜色分别把字符串绘制到两个BufferedImage中,然后根据两种颜色所占空间的比值分别擦除两个BufferedImage中的对应内容,最后再把两个BufferedImage合为一个. 效果图 可以通过以下代码更改两种颜色的比值 StringTwoColor stc = new StringTwoColor(jl1, Color.RED, Color.gray, 0.8);

  • 用python3教你任意Html主内容提取功能

    本文将和大家分享一些从互联网上爬取语料的经验. 0x1 工具准备 工欲善其事必先利其器,爬取语料的根基便是基于python. 我们基于python3进行开发,主要使用以下几个模块:requests.lxml.json. 简单介绍一个各模块的功能 01|requests requests是一个Python第三方库,处理URL资源特别方便.它的官方文档上写着大大口号:HTTP for Humans(为人类使用HTTP而生).相比python自带的urllib使用体验,笔者认为requests的使用体

  • Python3爬虫爬取英雄联盟高清桌面壁纸功能示例【基于Scrapy框架】

    本文实例讲述了Python3爬虫爬取英雄联盟高清桌面壁纸功能.分享给大家供大家参考,具体如下: 使用Scrapy爬虫抓取英雄联盟高清桌面壁纸 源码地址:https://github.com/snowyme/loldesk 开始项目前需要安装python3和Scrapy,不会的自行百度,这里就不具体介绍了 首先,创建项目 scrapy startproject loldesk 生成项目的目录结构 首先需要定义抓取元素,在item.py中,我们这个项目用到了图片名和链接 import scrapy

  • 手把手教你利用opencv实现人脸识别功能(附源码+文档)

    目录 一.环境 二.使用Haar级联进行人脸检测 三.Haar级联结合摄像头 四.使用SSD的人脸检测 五. SSD结合摄像头人脸检测 六.结语 一.环境 pip install opencv-python python3.9 pycharm2020 人狠话不多,直接上代码,注释在代码里面,不说废话. 二.使用Haar级联进行人脸检测 测试案例: 代码:(记得自己到下载地址下载对应的xml) # coding=gbk """ 作者:川川 @时间 : 2021/9/5 16:3

  • 10分钟教你用Python实现微信自动回复功能

     01 前言&&效果展示 相信大家都有忙碌的时候,不可能一直守在微信上及时回复消息.但微信又不能像QQ一样设置自动回复.无妨,今天,我们就来用Python实现微信的自动回复功能吧,并且把接收到的消息统一发送到文件助手里面,方便统一查看. 效果如下: 02 环境准备 Python版本:3.6.0 系统平台:Windows 10 X64 IDE:pycharm 相关模块: time模块: itchat模块: 以及一些Python自带的模块. 03 实现原理 其实原理很简单,主要是利用itcha

  • Pyqt实现简易计算器功能

    本文实例为大家分享了Pyqt实现简易计算器的具体代码,供大家参考,具体内容如下 环境:pycharm.python3.7 首先用qtDesigner设计出如下界面. 再用pyUIC将.ui文件转化内.py文件.源码如下 # -*- coding: utf-8 -*-   # Form implementation generated from reading ui file 'calculator.ui' # # Created by: PyQt5 UI code generator 5.11.

  • 三步教你开启IIS的GZIP压缩功能

    HTTP压缩 HTTP压缩是在Web服务器和浏览器间传输压缩文本内容的方法.HTTP压缩采用通用的压缩算法如GZIP等压缩HTML.JavaScript或CSS文件.压缩的最大好处就是降低了网络传输的数据量,从而提高客户端浏览器的访问速度.当然,同时也会增加一点点服务器的负担.GZIP是比较常见的一种HTTP压缩算法. 部署方法 1. 打开Internet信息服务(IIS)管理器,右击"网站"->"属性",选择"服务".在"HTT

  • 使用远程桌面连接Windows 2003 & 2008服务器详细图文教程

    云服务器系统是windows系列的用户,可以使用自己电脑windows系统自带的"远程桌面连接"功能远程连接云服务器.Windows2003/2008系统自带正版激活,不收取你系统正版激活费用,同时 windows2003/2008系统默认允许最多2个session远程连接,如果您需要更多的新增连接数,需要您向微软单独购买远程桌面授权(RD 授权)服务,RD授权费用需要您自理. 操作如下: 1.选择"开始菜单"-"所有程序"-"附件&q

随机推荐