看完这篇博文你一定会大有收获。在这个细分的Python领域,本人已经走在了全国最前沿。这是用turtle模块制作的多媒体案例。本人制作的东西太多,今天发一个大招。本篇摘自本人撰写的《哪咤学python进阶篇》之选学案例三:白桦林的故事.py。
有一首著名的歌叫《白桦林》,讲的是一个凄美的爱情故事。以下我们先来欣赏一下歌词,如下所示:
静静的村庄飘着白的雪
阴霾的天空下鸽子飞翔
白桦树刻着那两个名字
他们发誓相爱用尽这一生
有一天战火烧到了家乡
小伙子拿起枪奔赴边疆
心上人你不要为我担心
等着我回来在那片白桦林
天空依然阴霾依然有鸽子在飞翔
谁来证明那些没有墓碑的爱情和生命
雪依然在下那村庄依然安详
年轻的人们消失在白桦林
噩耗声传来在那个午后
心上人战死在远方沙场
她默默来到那片白桦林
望眼欲穿地每天守在那里
长长的路呀就要到尽头
那姑娘已经是白发苍苍
她时常听他在枕边呼唤
来吧亲爱的,来这片白桦林
天空依然阴霾依然有鸽子在飞翔
谁来证明那些没有墓碑的爱情和生命
雪依然在下那村庄依然安详
年轻的人们消失在白桦林
在死的时候她喃喃地说
我来了,等着我在那片白桦林
本案例是把这首歌播放出来,并且同时显示歌词。为了渲染气氛,加了一个阴天般的图片和粒子效果。粒子效果用的是很多小白点从屏幕随机往下移动。每个粒子都继承自海龟类。它们的形状都是dot,不过dot并不是海龟画图自带的形状。由于用circle形状也无法缩到最小的形状,所以作者自定义了一个最小的形状。它的顶点坐标为((0,0),(0,0))。 这个作品最关键的是在唱歌的时候歌词要同步显示。 所以就有了歌词文件的概念。它记录了每句歌词应该在哪个时间显示。以下是歌词文件里的两行。
[00:26.92]静静的村庄飘着白的雪
[00:33.27]阴霾的天空下鸽子飞翔
在上面的中括号里冒号前面的是分钟,后面是秒。在点号后面是毫秒的10倍值。我们可以利用time模块获取时间,当到达了某个时间点就显示那句歌词。这是完全可以的,显示歌词用一只海龟对象的write方法即可。不过在本作品中并没有采用上述所说的方法。为了配合ontimer命令的用法。我们把歌词文件的内容进行转换,主要是把中括号中的时间转换成了歌词应该显示多长时间,当然,并没有保存到磁盘,它只是在内存里。这是由musicwithlrc模块里make_septime_lrc函数完成的。它返回septime_lrc这个列表。这个列表的每一项是一个二元组。它如下图右边文字所示:
右边的10000, '白桦林'这句歌词就表示'白桦林'这三个字要显示10秒。算法是很简单的,只是把左图中第二行的时间减去上一行的时间。具体表现在make_septime_lrc函数中的这一句:sep = next_time - current_time。
以下是musicwithlrc模块的内容:
"""musicwithlrc.py。本程序定义两个函数,返回唱词的毫秒间隔时间和歌词。"""
__author__ = "李兴球"
__date__ = "2019/2/20"
def convert_to_msecond(line):
"""把lrc歌词文件中里中括号里的唱点时间转换成毫秒值,
即把'[00:05.25]歌词'这样的转换成(650,歌词),以元组形式返回。"""
sentence = line.split("]")[-1] # 歌词
time = line.split("]")[0] # 中括号里的时间
time = time[1:] # 下面把00:10.00这样的转换成毫秒
items = time.split(".")
items = [item.strip() for item in items] # 剥皮处理
ps = int(items[-1]) * 10 # 毫秒
time = items[0] # 01:33形式的时间
se = int(time.split(":")[-1]) * 1000 # 秒转换成毫秒
mi = int(time.split(":")[0]) * 60 * 1000 # 分转换成毫秒
return (mi + se + ps,sentence)
def make_septime_lrc(lrcfile):
"""生成歌词的间隔时间表,给海龟画图的ontimer用,返回列表,列表中的项目是二元组。
二元组的内容是歌词显示的时间和歌词。 """
septime_lrc = [] # 存放每行应该显示的时间和歌词
fc = [] # 存放每一行
f = open(lrcfile,encoding='utf-8')
for line in f:
line = line.strip()
if len(line)>10:fc.append(line)
f.close()
amounts = len(fc)
# 第一行的内容,它的歌词显示时间为第二行的时间减去它的时间
current_items = convert_to_msecond(fc[0])# 唱到当前行的时间和歌词
for index in range(amounts-1): # 最后一行不需要显示时间
next_line = fc[index+1] # 下一行文件内容
next_items = convert_to_msecond(next_line) # 唱到下一行的时间和歌词
current_time = current_items[0] # 唱到此行的毫秒数
next_time = next_items[0] # 唱到下一行的毫秒数
sep = next_time - current_time # 当前行应该显示的毫秒数
septime_lrc.append((sep,current_items[1].strip()))
current_items = next_items
return septime_lrc
if __name__ == "__main__":
lrc_for_ontimer = make_septime_lrc("白桦林.lrc")
for lrc in lrc_for_ontimer:
print(lrc)
上面的函数通过from musicwithlrc import * 被导入到白桦林的故事.py程序文件中。 在主程序中定义了名为sing的函数。它的参数为wav音乐文件名和歌词文件名。由它来播放音乐和同步显示歌词。同步显示歌词是在sing函数内再定义一个名为display_lrc的无参函数,由它调用屏幕的ontimer功能循环显示歌词。
在播放音乐的时候还要同步模拟下雪效果。这是通过首先定义一个叫Snow的类,然后由它实例化一些对象,具体由这些白色的小小粒子不断地从上到下移动而实现的。经过前面的学习,相信读者能读懂Snow类。
本案例新的知识是关于tkinter的。turtle模块是用tkinter开发的。在turtle.py里定义了_Root类。它继承自Tk类。当海龟画图启动的时候就是由它来实例化一个窗口的。以下是turtle.py中_Root类的源代码(中文注释为本书作者所加):
class _Root(TK.Tk):
"""Root class for Screen based on Tkinter."""
def __init__(self):
TK.Tk.__init__(self) # 生成窗口
def setupcanvas(self, width, height, cwidth, cheight):
self._canvas = ScrolledCanvas(self, width, height, cwidth, cheight) # 新建滚动画布对象
self._canvas.pack(expand=1, fill="both") # 放置画布
def _getcanvas(self):
return self._canvas
def set_geometry(self, width, height, startx, starty):
self.geometry("%dx%d%+d%+d"%(width, height, startx, starty))
def ondestroy(self, destroy):
"""使用协议机制来定义关闭窗口时触发的事件"""
self.wm_protocol("WM_DELETE_WINDOW", destroy) # 关闭窗口事件
def win_width(self):
return self.winfo_screenwidth()
def win_height(self):
return self.winfo_screenheight()
在turtle.py中有_Screen类,它有一个属性叫_root。而这个_root就是根窗口。所以在海龟画图中用screen._root就能直接访问窗口对象。下面是白桦林的故事.py的源代码,相信读者经过上面的阅读配合注释就能看懂代码。
"""白桦林的故事.py,本程序新建Snow类生成粒子效果做为雪花飘落。
名为sing的函数生成一只海龟,用来在屏幕上显示歌词并播放音乐。"""
__author__ = "李兴球"
__date__ = "2019/2/20"
from turtle import Turtle,Screen
from random import randint,choice
from musicwithlrc import *
from winsound import PlaySound,SND_ASYNC
from tkinter import messagebox
def sing(filename,srcfilename):
"""播放音乐并显示歌词"""
ziti = ("楷体",22,"normal") # 字体样式
time_and_lrc = make_septime_lrc(srcfilename)# 时间和歌词
lrc_amounts = len(time_and_lrc) # 歌词数量
writer = Turtle(visible=False) # 此作者是海龟
writer.penup() # 抬笔
writer.sety(30) # 设定y坐标
writer.color("blue") # 字的颜色
PlaySound(filename,SND_ASYNC) # 播放音乐
lrc_index = 0 # 歌词索引从0开始
def display_lrc(): # 定义显示歌词函数
"""从列表获取歌词显示出来"""
nonlocal lrc_index # 非本地变量
if lrc_index < lrc_amounts: # 索引小于数量则显示歌词
septime,lrc = time_and_lrc[lrc_index]
writer.clear() # 写新歌词前先擦掉
writer.write(lrc,align='center',font=ziti) # 显示歌词
writer.screen.ontimer(display_lrc,septime) # 时间到显示下一句
lrc_index = lrc_index + 1 # 索引加1
display_lrc()
class Snow(Turtle):
def __init__(self):
Turtle.__init__(self,shape='dot',visible=False)
self.penup()
self.color("white") # 雪花颜色
self.sh = self.screen.window_height() # 屏幕高度
self.sw = self.screen.window_width() # 屏幕宽度
self.init()
def init(self):
self.ht() # 隐藏自己
x = randint(-self.sw//2,self.sw//2) # 设置x坐标
y = randint(self.sh,self.sh*2) # 设置y坐标
self.goto(x,y) # 坐标定位
self.xspeed = randint(-1,1) # 横向速度
self.yspeed = randint(-2,-1) # 垂直速度
self.st() # 显示自己
def move(self):
"""根据x和y速度移动"""
x = self.xcor() + self.xspeed # 水平坐标增加横向速度
y = self.ycor() + self.yspeed # 垂直坐标增加垂直速度
self.goto(x,y)
if y < -self.sh//2 : # 到屏幕最低就重新init
self.init()
def close_window():
"""单击屏幕把running设为False,这样while循环就结束了"""
global running
if messagebox.askokcancel("白桦林", "你真的要离开吗?"):
running = False
if __name__ == "__main__":
counter = 0 # 计数器变量
amounts = 150 # 设定粒子总数
width,height = 480,360 # 定义屏幕宽高
running = True # 运行标志
screen = Screen() # 新建屏幕
screen.addshape("dot",((0,0),(0,0)))# 设定dot形状
screen.setup(width,height) # 设定屏幕宽高
screen.bgcolor("black") # 设定屏幕背景
screen.bgpic("bg.png") # 设定背景图片
screen.title("白桦林的故事_程序制作:李兴球")# 设定标题
screen.delay(0) # 设定屏幕延时
screen.onclick(lambda x,y:close_window()) # 单击屏幕关窗
root = screen._root # _root继承自Tk类
# 下一句表示当按窗口关闭按钮时调用close_window函数
root.ondestroy(close_window)
sing("白桦林.wav","白桦林.lrc") # 显示歌词放音乐
ps = [] # 定义粒子列表
while running: # 进入循环
if counter < amounts: # 未达指定数量
p = Snow() # 生成雪花粒子
ps.append(p) # 添加到列表
counter += 1 # 计数器加一
[p.move() for p in ps] # 移动每个粒子
screen.update() # 屏幕刷新重画
root.destroy() # 销毁窗口
无图无真相,要看作品的显示内容,请加本人抖音号即可!13507998321. ,在手机号后面有一个点!本人欲打造原创博客,域名就是我的名字lixingqiu.com。你可以收藏一下,敬请期待。