草庐IT

桌面太单调?一起用Python做个自定义动态壁纸,竟然还可以放视频!

Dragon少年 2023-10-28 原文

前言

前段时间,用PyQt5写了几篇文章,关于Python自制一款炫酷音乐播放器自定义桌面动画挂件车牌自动识别系统。今天就继续给大家分享一个实战案例,带大家一起用Python的PyQt5开发一个自定义动态桌面壁纸,好玩又有趣!

首先一起来看看最终实现的自定义动态壁纸效果:

下面,我们开始介绍这个自定义动态桌面的制作过程。

直接跳到文末获取粉丝专属福利。

一、核心功能设计

总体来说,我们需要实现将自己喜欢的视频转成一个动态桌面,知识点主要包含了对视频提取解析视频轮播PyQt5窗体设置桌面句柄获取自定义动态桌面壁纸实现等。

拆解需求,大致可以整理出我们需要分为以下几步完成:

  1. UI排版布局设计,确认动态壁纸功能设计
  2. 加载视频,对视频进行预览读取,保存视频路径等
  3. 动态壁纸功能实现应用,获取桌面句柄,轮播加载视频
  4. 关闭动态壁纸,在线壁纸资源获取等

二、实现步骤

之前有粉丝反馈说,想自己跟着文章自己敲敲代码,但是不知道具体需要哪些模块、包文件,后面我就把所有用到模块先放出来。

import os
import sys
from subprocess import call
from threading import Thread
from time import sleep

import cv2
from PyQt5 import QtCore,  QtWidgets
from PyQt5.QtCore import Qt,  QTimer
from PyQt5.QtGui import QImage, QPixmap, QIcon

from PyQt5.QtWidgets import QGridLayout, QPushButton, QMainWindow, QFileDialog, QLabel, QSystemTrayIcon, \
    QAction, QMenu, QMessageBox
from os import path as pathq

1. UI排版布局设计

根据动态壁纸所需要的功能,首先进行UI布局设计,我们这次还是使用的pyqt5。主要包含了加载读取本地视频、视频加载预览、动态壁纸应用、动态壁纸关闭等。核心设计代码如下:

# author:CSDN-Dragon少年
def setupUi(self, MainWindow):
    MainWindow.setObjectName("MainWindow")
    MainWindow.resize(505, 615)
    MainWindow.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
    self.centralwidget = QtWidgets.QWidget(MainWindow)
    self.centralwidget.setObjectName("centralwidget")
    self.pushButton = QtWidgets.QPushButton(self.centralwidget)
    self.pushButton.setGeometry(QtCore.QRect(22, 10, 89, 31))
    self.pushButton.setObjectName("pushButton")
    self.pushButton.clicked.connect(self.openmp4)
    self.pushButton.setStyleSheet(
        '''QPushButton{background:#F7D674;border-radius:5px;}QPushButton:hover{background:yellow;}''')
    self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
    self.groupBox.setGeometry(QtCore.QRect(22, 50, 452, 351))
    self.groupBox.setObjectName("groupBox")
    self.widget = QtWidgets.QWidget(self.groupBox)
    self.widget.setGeometry(QtCore.QRect(11, 20, 430, 291))
    self.widget.setObjectName("widget")
    self.gridLayout_3 = QtWidgets.QGridLayout(self.widget)
    self.gridLayout_3.setObjectName("gridLayout_3")
    self.label = QLabel(self)
    self.label.resize(400, 300)
    self.label.setText("Waiting for video...")
    self.gridLayout_3.addWidget(self.label)
    self.close_widget = QtWidgets.QWidget(self.centralwidget)
    self.close_widget.setGeometry(QtCore.QRect(420, 0, 93, 41))
    self.close_widget.setObjectName("close_widget")
    self.close_layout = QGridLayout()  # 创建左侧部件的网格布局层
    self.close_widget.setLayout(self.close_layout)  # 设置左侧部件布局为网格
    self.left_close = QPushButton("")  # 关闭按钮
    self.left_close.clicked.connect(self.close)
    self.left_visit = QPushButton("")  # 空白按钮
    #self.left_visit.clicked.connect(MainWindow.big)
    self.left_mini = QPushButton("")  # 最小化按钮
    self.left_mini.clicked.connect(MainWindow.mini)
    self.close_layout.addWidget(self.left_mini, 0, 0, 1, 1)
    self.close_layout.addWidget(self.left_close, 0, 2, 1, 1)
    self.close_layout.addWidget(self.left_visit, 0, 1, 1, 1)
    self.left_close.setFixedSize(15, 15)  # 设置关闭按钮的大小
    self.left_visit.setFixedSize(15, 15)  # 设置按钮大小
    self.left_mini.setFixedSize(15, 15)  # 设置最小化按钮大小
    self.left_close.setStyleSheet(
        '''QPushButton{background:#F76677;border-radius:5px;}QPushButton:hover{background:red;}''')
    self.left_visit.setStyleSheet(
        '''QPushButton{background:#F7D674;border-radius:5px;}QPushButton:hover{background:yellow;}''')
    self.left_mini.setStyleSheet(
        '''QPushButton{background:#6DDF6D;border-radius:5px;}QPushButton:hover{background:green;}''')
    self.horizontalLayout = QtWidgets.QHBoxLayout(self.close_widget)
    self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
    self.horizontalLayout.setObjectName("horizontalLayout")
    self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
    self.pushButton_2.setGeometry(QtCore.QRect(77, 440, 133, 41))
    self.pushButton_2.setObjectName("pushButton_2")
    self.pushButton_2.clicked.connect(self.play)
    self.pushButton_2.setStyleSheet(
        '''QPushButton{background:#6DDF6D;border-radius:5px;}QPushButton:hover{background:green;}''')
    self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
    self.pushButton_3.setGeometry(QtCore.QRect(308, 440, 111, 41))
    self.pushButton_3.setObjectName("pushButton_3")
    self.pushButton_3.clicked.connect(self.close_wall)
    self.pushButton_3.setStyleSheet(
        '''QPushButton{background:#F76677;border-radius:5px;}QPushButton:hover{background:red;}''')
    self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget)
    self.pushButton_4.setGeometry(QtCore.QRect(187, 540, 133, 21))
    self.pushButton_4.setObjectName("pushButton_4")
    self.pushButton_4.clicked.connect(self.openurl)
    self.pushButton_4.setStyleSheet(
        '''QPushButton{background:#222225;color:white;border-radius:5px;}QPushButton:hover{background:#222225;color:skyblue}''')
    MainWindow.setCentralWidget(self.centralwidget)
    self.menubar = QtWidgets.QMenuBar(MainWindow)
    self.menubar.setGeometry(QtCore.QRect(0, 0, 505, 23))
    self.menubar.setObjectName("menubar")
    MainWindow.setMenuBar(self.menubar)
    self.statusbar = QtWidgets.QStatusBar(MainWindow)
    self.statusbar.setObjectName("statusbar")
    MainWindow.setStatusBar(self.statusbar)
    self.retranslateUi(MainWindow)
    QtCore.QMetaObject.connectSlotsByName(MainWindow)
    self.groupBox.setStyleSheet('''
    color:white
    ''')
    MainWindow.setWindowOpacity(0.95)  # 设置窗口透明度
    MainWindow.setAttribute(Qt.WA_TranslucentBackground)
    MainWindow.setWindowFlag(Qt.FramelessWindowHint)  # 隐藏边框
# author:Dragon少年
def retranslateUi(self, MainWindow):
    _translate = QtCore.QCoreApplication.translate
    MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
    self.pushButton.setText(_translate("MainWindow", "从本地选择"))
    self.groupBox.setTitle(_translate("MainWindow", "预览"))
    self.pushButton_2.setText(_translate("MainWindow", "应用"))
    self.pushButton_3.setText(_translate("MainWindow", "关闭壁纸"))
    self.pushButton_4.setText(_translate("MainWindow", "在线资源"))

UI实现效果如下:

UI布局设计完成,下面我们开始进行视频读取加载、预览功能实现。

2. 视频加载预览

接来下我们可以根据自己喜欢的视频,从本地读取视频,并且将视频预览播放显示。这里视频演示,博主还是用之前的那篇紫颜小姐姐的跳舞视频进行演示。

读取视频:

读取视频我们可以通过打开文件对话框,选择视频资源,开启一个子线程用来进行视频开启停止播放。核心代码如下:

# author: CSDN-Dragon少年
def openmp4(self):
    try:
        global path
        path, filetype = QFileDialog.getOpenFileName(None, "选择文件", '.',
                                                     "视频文件(*.AVI;*.mov;*.rmvb;*.rm;*.FLV;*.mp4;*.3GP)")  # ;;All Files (*)
        if path == "":  # 未选择文件
            return

        self.slotStart()
        t = Thread(target=self.Stop)
        t.start()  # 启动线程,即让线程开始执行
    except Exception as e:
        print (e)

视频流读取播放:

接下来,我们需要对视频文件进行按帧读取加载显示,并通过计时器实现动画效果。核心代码如下:

# author:CSDN-Dragon少年
def slotStart(self):
    videoName = path
    if videoName != "":  # “”为用户取消
        self.cap = cv2.VideoCapture(videoName)
        self.timer_camera.start(50)
        self.timer_camera.timeout.connect(self.openFrame)
# author:CSDN-Dragon少年
def openFrame(self):
    if (self.cap.isOpened()):
        ret, self.frame = self.cap.read()
        if ret:
            frame = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
            if self.detectFlag == True:
                # 检测代码self.frame
                self.label_num.setText("There are " + str(5) + " people.")
            height, width, bytesPerComponent = frame.shape
            bytesPerLine = bytesPerComponent * width
            q_image = QImage(frame.data, width, height, bytesPerLine,
                             QImage.Format_RGB888).scaled(self.label.width(), self.label.height())
            self.label.setPixmap(QPixmap.fromImage(q_image))
        else:
            self.cap.release()
            self.timer_camera.stop()  # 停止计时器

至此,我们已经可以实现视频读取加载,并且进行视频预览了,效果如下:

3. 动态壁纸功能实现

实现桌面壁纸替换,我们首先需要获取桌面句柄找到桌面窗体,覆写桌面窗体、调用加载的视频流,播放动态壁纸。

获取桌面句柄:

# author:CSDN-Dragon少年
def pretreatmentHandle():
    hwnd = win32gui.FindWindow("Progman", "Program Manager")
    win32gui.SendMessageTimeout(hwnd, 0x052C, 0, None, 0, 0x03E8)
    hwnd_WorkW = None
    while 1:
        hwnd_WorkW = win32gui.FindWindowEx(None, hwnd_WorkW, "WorkerW", None)
        if not hwnd_WorkW:
            continue
        hView = win32gui.FindWindowEx(hwnd_WorkW, None, "SHELLDLL_DefView", None)
        # print('hwmd_hView: ', hView)
        if not hView:
            continue
        h = win32gui.FindWindowEx(None, hwnd_WorkW, "WorkerW", None)
        while h:
            win32gui.SendMessage(h, 0x0010, 0, 0)  # WM_CLOSE
            h = win32gui.FindWindowEx(None, hwnd_WorkW, "WorkerW", None)
        break
    return hwnd

桌面覆写:

我们可以创建一个类,对窗体进行继承,进行视频流加载读取播放。核心代码如下:

# author: CSDN-Dragon少年
class MyMainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.player = QMediaPlayer()
        self.player.setNotifyInterval(10000)
        self.player.setVideoOutput(self.ui.videowidget)
        self.player.setMuted(bool(1 - self.player.isMuted()))
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setupUi(self)
        self.go()


	# author: CSDN-Dragon少年
    def go(self):
        self.ui.videowidget.setFullScreen(True)

        with open("./filename.txt", 'r', encoding='utf-8') as f:
            file_name = f.read()
            if file_name =='':
                file_name = 'lkf.mp4'
        print (file_name)
        if not os.path.exists(file_name):
            sys.exit()
        media = QMediaContent(QUrl(file_name))
        self.player.setMedia(media)
        self.mplayList = QMediaPlaylist()
        self.mplayList.addMedia(QMediaContent(QUrl.fromLocalFile(file_name)))
        self.player.setPlaylist(self.mplayList)
        self.mplayList.setPlaybackMode(QMediaPlaylist.CurrentItemInLoop)
        win_hwnd = int(self.winId())
        video_h = int(self.ui.videowidget.winId())
        win32gui.SetParent(win_hwnd, h)
        win32gui.SetParent(video_h, h)
        win32gui.SetParent(video_h, win_hwnd)
        self.player.play()

这里我们把上面的py文件直接打包成exe文件,接下来我们在“应用”控件上进行事件绑定,直接调用exe执行,实现动态壁纸播放应用功能。核心代码如下:

# author: CSDN-Dragon少年
def play(self):
     if path == '':
         reply = QtWidgets.QMessageBox.question(self, '提示',
                                                "未加载选择视频",
                                                QtWidgets.QMessageBox.Yes)
         return
     with open("./filename.txt", 'w', encoding='utf-8') as f:
         f.truncate(0)
         print(f.write(str(path)))
     try:
         try:
             call('taskkill /F /IM play.exe')
         except:
             pass
         os.system('start play.exe')
     except:
         pass
     try:
         if self.cap != []:
             self.cap.release()
             self.timer_camera.stop()  # 停止计时器
         else:
             Warming = QMessageBox.warning(self, "Warming", "Push the left upper corner button to Quit.",
                                           QMessageBox.Yes)
     except:
         pass

这样,我们就完成了动态壁纸加载应用功能了,效果如下:

4. 关闭动态壁纸

最后我们再实现下当前动态壁纸播放关闭功能,我们需要对当前桌面视频播放进行释放取消。代码如下:

# author:CSDN-Dragon少年
 def close_wall(self):
     try:
         call('taskkill /F /IM play.exe')
     except:
         pass

效果如下:

至此,整个自定义动态桌面壁纸功能就全部完成了,下面我们一起运行下看看动态壁纸效果。

源码及数据已上传,关注文末公众号回复【源码】即可获取完整源码

Python往期精彩:

有关桌面太单调?一起用Python做个自定义动态壁纸,竟然还可以放视频!的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  3. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  4. ruby - 我可以使用 Ruby 从 CSV 中删除列吗? - 2

    查看Ruby的CSV库的文档,我非常确定这是可能且简单的。我只需要使用Ruby删除CSV文件的前三列,但我没有成功运行它。 最佳答案 csv_table=CSV.read(file_path_in,:headers=>true)csv_table.delete("header_name")csv_table.to_csv#=>ThenewCSVinstringformat检查CSV::Table文档:http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV/Table.html

  5. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  6. ruby - 我可以使用 aws-sdk-ruby 在 AWS S3 上使用事务性文件删除/上传吗? - 2

    我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的

  7. ruby - 有人可以帮助解释类创建的 post_initialize 回调吗 (Sandi Metz) - 2

    我正在阅读SandiMetz的POODR,并且遇到了一个我不太了解的编码原则。这是代码:classBicycleattr_reader:size,:chain,:tire_sizedefinitialize(args={})@size=args[:size]||1@chain=args[:chain]||2@tire_size=args[:tire_size]||3post_initialize(args)endendclassMountainBike此代码将为其各自的属性输出1,2,3,4,5。我不明白的是查找方法。当一辆山地自行车被实例化时,因为它没有自己的initialize方法

  8. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  9. ruby - 我可以将我的 README.textile 以正确的格式放入我的 RDoc 中吗? - 2

    我喜欢使用Textile或Markdown为我的项目编写自述文件,但是当我生成RDoc时,自述文件被解释为RDoc并且看起来非常糟糕。有没有办法让RDoc通过RedCloth或BlueCloth而不是它自己的格式化程序运行文件?它可以配置为自动检测文件后缀的格式吗?(例如README.textile通过RedCloth运行,但README.mdown通过BlueCloth运行) 最佳答案 使用YARD直接代替RDoc将允许您包含Textile或Markdown文件,只要它们的文件后缀是合理的。我经常使用类似于以下Rake任务的东西:

  10. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

随机推荐