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