当前位置:网站首页>如何在 pyqt 中实现桌面歌词
如何在 pyqt 中实现桌面歌词
2022-07-24 03:05:00 【qq_43479892】
优质资源分享
| 学习路线指引(点击解锁) | 知识定位 | 人群定位 |
|---|---|---|
| 🧡 Python实战微信订餐小程序 🧡 | 进阶级 | 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。 |
| Python量化交易实战 | 入门级 | 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统 |
前言
酷狗、网抑云和 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,以上~~
边栏推荐
- 日常杂谈(一)
- Summernote rich text editor
- go strconv
- Connected graph (day 72)
- Go log package
- Detailed explanation of wechat official account online customer service access methods and functions
- 开源量子开发框架 Cirq
- The implementation in unity determines whether missing or null
- The solution of using non root user management in secure stand-alone database under general machine environment
- The simple use of ADB command combined with monkey is super detailed
猜你喜欢

The function of SIP account - tell you what is SIP line

To forge ahead on a new journey, the city chain science and technology carnival was grandly held in Xiamen

In the future, when the interviewer asks why you don't recommend using select *, please answer him loudly!

Recommendation system topic | recommendation system architecture and single domain cross domain recall model

Analyze the space practice field required by maker education activities

SolidWorks cannot reference references

SIGIR‘22 推荐系统论文之多样性篇

String.split() the most detailed source code interpretation and precautions

Services et configurations FTP

Skywalking distributed system application performance monitoring tool - upper
随机推荐
CMT 注册——Google Scholar Id,Semantic Scholar Id,和 DBLP Id
Take you into the world of MySQL mvcc
SolidWorks cannot reference references
Type de symbole
Is it safe to open an account for Xiaobai stock? Can I apply online?
go IO操作-文件读
Analyze the overall planning of steam and maker education classroom
summernote 字体显示中文
Ugui source code analysis - iclippable
【英雄哥七月集训】第 23天: 字典树
开源量子开发框架 Cirq
openEuler 资源利用率提升之道 01:概论
go errors
Minimum exchange times
What is the security of Treasury reverse repo
O3DE 的Lumberyard 游戏引擎
Unity 消息推送
微信公众号在线客服接入发方法和功能详解
PMP preparation experience | good habits, good process, good results
Ways to improve the utilization of openeuler resources 01: Introduction
