基于Turtle库和PyQt的生日礼物设计

设计初衷:

写这篇文章,就是想记录一下,在做这个设计的历程。
首先呢,想做这个单纯是觉得自己做的会更体现心意;还有其中的程序会借用到其他大佬的设计和思路,我个人只是搬运+修改!


设计历程

1.
起初我的打算是想用画图库turtle来画个蛋糕,但是奈何自己对这个库没基础,而且要想画出来好看的蛋糕很麻烦,各种细节很多…我就在网上找了个现有程序,自己修改了些。然后最后的结果就是下面的这个
在这里插入图片描述
用到的库也没几个

import time
import turtle as t
import math as m
import random as r
import pygame
from PyQt5.QtCore import QObject, pyqtSignal #这个后面要用

看的哪一篇博客的代码,我一时半会也找不到了…
但是到了这里感觉这礼物太潦草了,所以我就想着加个浪漫背景音乐。就在本站查了点资料,可以用pygame添加bgmusic!代码很简单,

def show_init():
    pygame.init()  # 初始化
    pygame.mixer.init()  # 混音器初始化

    # 设置背景音乐
    pygame.mixer.music.load("银河与星斗.mp3")
    pygame.mixer.music.set_volume(1)  # 设置音量

    bg_music = pygame.mixer.Sound("银河与星斗.mp3")
    bg_music.play()

这个我是在开始画图的时候,开始播放,但是到了最后打包会发现,运行的时候总是音乐先出来,所以我想可以把音乐开始放在开始画图的后面几句代码。
多说一句,我在导库的时候,如果用pip~就会很慢,不管是在pycharm终端,还是虚拟环境。所以我一般会用这句指令:

pip安装包超时报错解决:pip --default-timeout=100 install 模块名称 -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com (豆瓣源)
#也是看的某个大佬的博客

我本来是想着,音乐开始到结束配合着画图的整个过程*(音乐大概3分多钟,买了个本地音乐还花了2块大米…)*。但是画图太慢了,所以我就把原来代码的5根蜡烛变成了3根,而且把画图的速度前期调到最大。

 t.speed(10)
 #最快是10
 

在这里插入图片描述
到此,第一阶段设计完成。

2.
感觉作为一个生日礼物,只画个蛋糕加个音乐,呃,还是心意不够。所以我就再思考了一下。能不能后面再加上一个祝福视频什么的呢?有了目标就去找资料,但是我的基础不好,很多知识我都想不到。

逻辑问题:

既然要做肯定要做好一点,所以我刚开始的时候,计划是:在蛋糕画完之后就开始播放视频。但是涉及到,
~怎么在画完生日蛋糕就播放视频?
~用什么播放视频?
~生日蛋糕和视频播放的界面要同时存在怎么解决?


我原先的想法是:把生日蛋糕(Turtle库程序)封装为Birthday.py文件,然后在另一个Video.py文件中,实时检测Birthday.py中的标致变量flag的值,值的改变代表画图结束。在Video.py中根据flag的值做相应操作。所以呢,从文件的同时运行不冲突角度考虑,我想到了进程操作—把画生日蛋糕和播放视频的程序放在两个进程,这样就同时运行不冲突了。
But,不可能这么完美!
在这里插入图片描述
在画图结束时,程序里确实flag布尔值会改变,

import Birthday.py
global flag
flag=Birthday.flag#在Video.py中的指令,在while循环里

但是这样,我试了很多次,在Video.py中永远也得不到flag值的改变量。
后来,我上网查了点资料。有帖子说,while循环里操作,会导致程序的卡住…然后我用到了定时器,不使用while,在定时器的槽函数中检测flag的值:

def play():
    app = QtWidgets.QApplication(sys.argv)
    window = mywindow()

    # 创建定时器
    timer = QtCore.QTimer()
	#定时器槽函数
    def check_flag():
        flag = Birthday.flag  # 读取flag的当前值
        print("当前flag值:", flag[0])
        if flag[0] == 1:
            window.show()
            timer.stop()  # 停止定时器
    # 将槽函数与定时器的timeout信号连接
    timer.timeout.connect(check_flag)
    # 启动定时器
    timer.start(1000)  # 每隔1秒触发一次定时器
    print('定时器启动!')
    sys.exit(app.exec_())

而且也确保了Birthday.py文件中的flag是可供访问的全局变量。

# Birthday.py

# 定义全局变量 flag
global flag
flag =False # 初始化 flag 值

但是!这样做还不行!试了一个白天,都不知道哪的原因。在这里插入图片描述
在晚上的时候,我没办法了,就想着问问chatgpt…
在这里插入图片描述
有解决办法,就赶紧试试。发现可以!
然后我又问了下原理:

使用信号和槽机制可以实现实时检测flag的值,而不需要使用定时器。当flag的值在Birthday.py文件中修改时,通过发射信号,其他文件中的槽函数将立即对flag的变化进行处理。这种方法更加有效和高效,因为它减少了不必要的轮询和检测开销。而使用定时器会导致频繁的检查,可能会有一定的性能损失。

当flag值在Birthday.py文件中修改后,flag_changed信号将会发射。通过调用emit()方法,信号会传递flag的新值作为参数。一旦信号发射,所有连接到该信号的槽函数都会被调用。在我们的示例中,flag_handler.handle_flag_change()槽函数会被调用,并且会将flag的新值作为参数传递给该槽函数。这样,video.py文件中的handle_flag_change()函数就可以获取到flag的新值,并对其进行处理。你可以根据具体的需求,在handle_flag_change()函数中进行任何逻辑处理或更新用户界面等操作…

但是因为我的两个文件还是处于不同进程中,所以运行后就会报错。原因chatgpt是这样解释:

flag_updater.flag_changed.emit(flag)这行代码是在Birthday.py文件中执行的。由于默认情况下,信号和槽机制只能在同一个线程中工作,所以在Birthday.py文件中直接调用emit()函数发射信号,并不能触发其他文件中相应的槽函数。为了实现不同文件中的函数对信号的连接和发射,你需要考虑以下几点:
保证所有相关的文件都在同一个线程中运行。
使用合适的事件循环机制来处理信号和槽的连接和发射。

简单修改后,就可以完美运行。
附上主要的代码:

***Birthday.py:***
from PyQt5.QtCore import QObject, pyqtSignal

class FlagUpdater(QObject):#继承QObject类
    flag_changed = pyqtSignal(bool)

flag_updater=FlagUpdater()#实例化对象
#需要安装pyqt库
#如果想要告知其他文件flag值,就在合适的位置,添加如下:
flag_updater.flag_changed.emit(flag)
***Video.py:***
class FlagHandler(QObject):
    @pyqtSlot(bool)
    def handle_flag_change(self, flag):
        # 在这里处理flag的变化
        if flag==True:
            app = QtWidgets.QApplication(sys.argv)
            window = mywindow()
            window.show()
            sys.exit(app.exec_())
        else:
            pass


if __name__ == "__main__":
    flag_handler = FlagHandler()
    flag_updater.flag_changed.connect(flag_handler.handle_flag_change)
    Birthday.draw_play()
#省略了qt窗口的代码

这样就不用使用定时器或者循环检测。但是最后也是知道了对于跨进程的任务处理,着实有点麻烦。

到了这里还只是完成了一半…

3.
对于界面播放视频,想到的是GUI编程,我上网查了资料,觉得PyQt库比较好,控件多而且容易可视化操作。
对于可视化界面编辑,我是直接用的QtDesigner。这个可以直接打开qtdesigner.exe这个程序,制作好界面后选择保存位置就行;也可以添加到pycharm里面。我是添加到了pycharm。可以参考这个大佬的链接:
pycharm配置QtDisigner
在这里插入图片描述
添加完是这样。
附加命令行转换UI为py:

pyuic5 -o demo.py demo.ui     ---ui文件转py文件
#要切换到文件目录下 cd.....

逻辑就是在pycharm中打开QtDesigner,设计GUI界面,保存;使用PyUIC把UI文件转换成py文件(QtDesigner保存的是UI文件,在项目中需要转换成py文件后才能使用)。

因为我只是在窗口添加媒体播放的控件,很好操作。唯一注意的是,我要添加的控件是QVideoWidget类,但是在qt的控件库没有这个,所以把基类QWidget提升到QVideoWidget
具体的操作我是看的哔站上一个视频。播放本地视频

嗯~,经过一系列的操作,应该会是下面这样:
在这里插入图片描述
大概率都会有最底下这句话的。。。
这是我整个的GUI代码:

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(749, 621)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.widget = QVideoWidget(self.centralwidget)
        self.widget.setGeometry(QtCore.QRect(20, 20, 711, 561))
        self.widget.setObjectName("widget")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 749, 22))
        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)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
from PyQt5.QtMultimediaWidgets import QVideoWidget

GUI代码封装到单独一个文件是比较方便的。但是编辑完UI程序只是说给它一个空壳,具体的界面呈现和操作都要继续做(比如为按钮控件添加触发事件)。因为我就是单纯的想用窗口播放视频,所以就只需要对QVideoWidget控件添加对应的响应就行—Qt窗口打后直接播放一个视频,并不循环,也没有视频列表,视频文件我直接放在项目文件目录下,程序会自动加载,所以很简单。

class mywindow(QtWidgets.QMainWindow,Video.Ui_MainWindow):
    def __init__(self):
        super(mywindow,self).__init__()
        self.setupUi(self)
        # 创建视频播放器实例
        self.player = QMediaPlayer()
        # 创建一个播放列表实例
        self.play_list = QMediaPlaylist()
        # 把视频播放器放入对应的组件里(PyQt5.QtMultimediaWidgets.QVideoWidget)
        self.player.setVideoOutput(self.widget)
        media_path=PyQt5.QtCore.QUrl('file:mygirl.mp4')#视频路径。可以加绝路径
        print(media_path)#这句不要
        #self.play_list.addMedia(QMediaContent)
        self.player.setMedia(QMediaContent(media_path))
        self.play_list.setPlaybackMode(QMediaPlaylist.PlaybackMode.Loop)
        #self.player.setPlaylist(self.play_list)
        self.player.play()

到了这里,功能已经初步实现了。
在这里插入图片描述
点击运行VideoPlay.py,可以正常运行。先出现生日蛋糕的界面,然后播放视频的qt界面,会一直存在,不会自己退出,直到点击×。
在这里插入图片描述
~~
在这里插入图片描述

4.
现在已经可以在自己电脑上运行无误了。但是还有最后一步—打包项目成exe文件。

由于别人电脑木有python环境和库文件,所以要把一些必要数据一起打包。比如音乐和视频文件,exe文件logo图片…最重要的是一些依赖包。因为我的工程中有三个py文件,并且py文件之间互有联系,所以多文件打包。但是主文件只有一个,也就是程序的运行开始文件有一个,打包的时候要从这个主文件下手。

嗯~,我原先不会多文件打包,上网搜了一下,这个大佬博客讲的挺不错。多文件打包
我按着步骤来,也踩了一些坑…我是在pycharm的终端里进行的,Python环境我有很多(主要是Anaconda里的)。

多说一下,使用conda虚拟环境是挺方便的,这样会节省不必要的麻烦。一个环境是一个小房间,根据应用场景不同,里面会有各种库.假如你要做人脸识别的项目,可以命名一个Face_Recognize,根据需要添加python的版本。各个环境互不干扰。

在这里插入图片描述
根据大佬的指引,先生成spec文件

pyi-makespec -option1 -option2 -... name.py
#参数上,我是用的-F(生成一个exe文件)
-w 隐藏控制台窗口(GUI程序最好加上,防止出现闪屏)
-p(我没用这个,因为打包的时候会根据自己导的包,自行添加。如果后面报错缺少包,可以加上)
-i 更换exe文件logo,我自己加了(把ico图片放到py文件目录下就行)
#推荐一个好用的在线转换ICO格式的网站,http://www.zuohaotu.com/image-to-ico.aspx 在线转换ico格式

编辑spec文件:在这里插入图片描述
我没有其他数据,就改了文件列表(最好py文件都在相同目录下)。

打包spec文件:

Pyinstaller VideoPlay.spec
#这个不要改什么参数,不然好像会报错。

!!!我就在最后一步错了,打包很多次,运行exe文件都失败。提示我pygame未找到。。我就纳闷了,我那个环境明明装了啊,为啥每次都报错…在网上找了很久很久,看到一个博主的提示:在这里插入图片描述
看到第一条,我就想到了什么。我用的环境是有pygame,但是在打包时的log提示中,一直显示的是py37?会不会是打包时用的都是这个环境?我就换了个环境,试了一下,成了…唉,无语了都,错误就在可以看的到的地方!!(我也不知道为啥老是用conda自带的python解释器)

打包之后,我运行了下exe文件,很丝滑,就是在刚开始启动exe的时候有点慢,因为它要搜索包。最后就是,exe文件一般在dist文件夹里,可以把它提到py文件目录下,跟音乐、视频同处一个屋檐下。有时会因为找不到视频文件报错。(如果是把数据放到专门的文件夹下,也可以。应该是要改spec文件,不过我自己没试过)
在这里插入图片描述
在这里插入图片描述
嗯~,我是把视频放进去了。idea和pycache应该是pycharm软件产生的文件。

最后,我把整个打包后发给别人,在别的电脑打开有一点问题。第二个qt窗口能出来,但是视频无法显示。。。也没有报错,就很纳闷。但是因为过年了,没时间找错,就先放在那了,过几天再弄弄。这篇文章是写给我自己的记录,因为有些东西老忘,方便自己以后看,滋滋滋。2024.2.14 1:07
在这里插入图片描述

  • 43
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值