使用 Kivy 和 KivyMD 构建 WhatsApp 状态保护应用程序
您有没有想过如何构建 WhatsApp 状态保护程序?也许您想将有趣的 WhatsApp 状态保存在您的设备上。你来对地方了!在本教程中,您将学习如何使用 Python 构建自己的 WhatsApp 状态保护程序。您将能够使用该应用程序将您的图像和视频保存在自定义目录中,并在您的图库中查看它们。
先决条件
-
Python 3或更高版本,从这里下载
-
沮丧 (
pip install Kivy) -
KivyMD (
pip install kivymd) -- 请注意,这只有在您已经安装了 Kivy 时才有效。 -
OpenCV (
pip install opencv-python)
了解应用程序背后的概念
您应该知道的一件事是,WhatsApp 有一个目录,用于暂时在您的手机上存储您的故事(仅图像和视频)以及您朋友的故事。您可以通过打开文件管理器然后导航到内部存储 > WhatsApp > 媒体 > .Statuses 来自己检查此目录。您在某些文件夹上看到的文件前面的点表示它们是临时的,就像缓存一样。这个想法是从文件夹中过滤掉图像和视频,但要分开;让我们称之为后端,因为我们的用户可能不知道发生了什么。您希望用户看到的只是不同屏幕上的图像和视频,他们可以在其中与应用程序交互并保存他们喜欢的图像或视频。让我们称之为前端,让我们使用代码的力量连接前端和后端。
模块/库以及您需要它们的用途
-
os- 您需要 os 模块来检查目录是否已经存在并为应用程序创建自定义目录。 -
cv2- OpenCV 库为您提供了一种通过读取视频文件并从帧生成 1000x600 大小的图像来为视频故事制作 png 缩略图的方法。 -
glob- 这个特殊的预安装 Python 模块允许您遍历目录并获取具有指定模式的文件名。 -
time- 因为您必须为每个保存的状态创建自定义文件名。您将使用 time 模块来获取当前时间,包括小时、分钟和基本上秒;因为包括第二个可以帮助您在保存图像或视频时避免文件名重复。用户可以在相同的日期、小时和分钟内保存,但不能在同一秒内保存。 -
shutil- 听说过这个吗?当您在手机或桌面上复制和移动文件时,此模块可以做到这一点。由于您从 WhatsApp 文件夹中获取故事,因此您需要将它们从文件夹复制到您使用os模块创建的自定义文件夹中。这允许您将来自源 (WhatsApp/Media/.Statuses) 的故事保存在您的自定义目录中 - (WSS/Images) 用于图像,(WSS/Videos) 用于视频。否则,如果您移动了它们,您会对 WhatsApp 应用程序上丢失的状态数量感到惊讶。别说我没警告过你。 -
datetime- 一个很好的模块,您可以获取当前日期,包括日、月和年(以数字表示),这将有助于您命名已保存的故事。 -
Kivy- 这个库允许你导入类并在你的代码中使用它们。 Kivy 需要的唯一类是Builder、Video、Window和StringProperty。Builder允许您将 KV 代码解析为主要 Python 代码,以便您可以从主应用程序类访问所有内容;Video,听起来是您将用来创建视频 WhatsApp 故事实例的类;Window允许您设置应用程序的屏幕尺寸,以模拟真实Android手机的屏幕尺寸。最后,StringProperty让您有机会设置自己的属性,以便您可以在类中使用它们。StringProperty在这个应用中的应用是创建一个label属性,它告诉每个图像的位置和图像的总数,这也适用于视频。当您看到 5/9 时,表示这是 9 个视频或图像总数中的第 5 个图像或视频。我推荐的尺寸是 360x640,因为这是 Figma 在创建 UI/UX 设计时用于 Android 框架的尺寸; -
KivyMD- 你将需要这个库中的许多类来设置应用程序的样式并创建漂亮的小部件,最重要的一个是你需要让你的应用程序运行的MDApp类。在您的驱动程序代码中,您将创建一个应用程序类的实例并使用run()方法为其提供一个窗口并让您的应用程序运行。
使用模板
为了使在 Python 中构建应用程序时更容易,我编写了一个自定义模板代码,并且在我想使用 Kivy 和 KivyMD 构建项目的任何时候复制该文件。该模板具有来自 Kivy 和 KivyMD 的基本导入; KV 代码(存储在 KV 变量中的文档字符串)包含一个带有 id 的屏幕管理器,我们可以稍后在代码中引用;三个空屏幕; App类及基本功能;您可以编辑的自定义函数;以及启动引擎(运行应用程序)的驱动程序代码。这是下面的模板代码,您可以按照自己喜欢的方式对其进行编辑,并在您想要使用 Python 构建的每个应用程序中使用它,只要确保您正在解决问题。
"""
项目:
经过:
上:
为:解决...的问题
"""
#进口
从 kivy.lang 导入生成器
从 kivymd.app 导入 MDApp
#KV
KV u003d """
屏幕管理器:
编号:sm
屏幕:
姓名: ””
屏幕:
姓名: ””
屏幕:
姓名: ””
"""
#类
类应用程序名称(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.screen u003d Builder.load_string(KV)
定义构建(自我):
返回self.screen
定义另一个函数(自我):
返回“没有?”
#驱动代码
如果 __name__ u003du003d "__main__":
AppName().run()
这是您需要安装的所有类和模块,您可以更新模板代码并运行应用程序。我通过使用Window类中的方法添加了窗口大小,它接受 (Width, Height) 的元组,在这种情况下,您将使用 (360, 640) 或根据您的选择所需的任何大小。运行应用程序时,您应该会看到没有小部件的空白屏幕,恭喜您成功运行应用程序。
#...
#进口
进口我们
导入简历2
导入全局
进口时间
进口开玩笑
从日期时间导入日期
从 kivy.lang 导入生成器
从 kivymd.app 导入 MDApp
从 kivymd.toast 导入吐司
从 kivy.uix.video 导入视频
从 kivy.core.window 导入窗口
从 kivymd.utils.fitimage 导入 FitImage
从 kivymd.uix.floatlayout 导入 MDFloatLayout
从 kivymd.uix.boxlayout 导入 MDBoxLayout
从 kivymd.uix.imagelist 导入 SmartTileWithLabel
从 kivy.properties 导入 StringProperty
窗口大小 u003d (360, 640)
将应用布局划分为屏幕
该模板有三个屏幕,应用程序的前两个屏幕就像主屏幕的子屏幕。我们可以切换两个选项卡来查看是仅查看图像还是仅查看视频,第三个屏幕将用于每个图像的全屏模式。这是用户单击的图像以全屏模式查看的位置,用户可以通过保存或向左或向右滑动来与之交互;此屏幕将有一个工具栏、一个指向前一个屏幕(主屏幕)的后退箭头,以及一种向左或向右滑动以查看图像的方法。我们应用程序中的最后一个屏幕用于播放视频,用户可以播放/暂停当前正在播放的视频,他们可以使用滑块进行搜索,并且几乎可以执行您可以使用视频播放器执行的所有基本操作。请注意,视频也可以从同一屏幕保存。
处理 KV 代码
首先要做的是从 Kivy 的 ScreenManager 模块中导入 NoTransition 类。您将需要它,因为它允许您在从一个屏幕移动到另一个屏幕时控制过渡。请注意,这仅适用于屏幕,不适用于图像和视频选项卡。您可以使用任何您想要的过渡,因为Kivy 网站上提供了许多选项。在此之后,您可以编写图像和视频图块的代码。 ``` #... KV u003d """ #: 导入 NoTransition kivy.uix.screenmanager.NoTransition :
MDI图标按钮:
图标:“全屏”
theme_text_color: "自定义"
文本颜色:(1、1、1、1)
pos_hint: {"center_x": .9, "center_y": .1}
on_release:
app.ImageFullscreen(root.source)
MDFloat 布局:
pos_hint: {"center_x": .85, "center_y":.92}
大小提示:0.2,0.1
画布之前:
颜色:
RGBA: (0, 0, 0, 1)
圆角矩形:
半径:[20,]
pos: self.pos
尺寸:self.size
MD标签:
pos_hint: {"center_x": .5, "center_y": .5}
文本:root.label
theme_text_color: "自定义"
文本颜色:1、1、1、1
粗体:真
size_hint_x: 1
对齐:“中心”
:
MDI图标按钮:
图标:“播放圈大纲”
theme_text_color: "自定义"
文本颜色:(1、1、1、1)
用户字体大小:“80sp”
pos_hint: {"center_x": .5, "center_y": .5}
on_release:
app.VideoFullscreen(root.source)
MDFloat 布局:
zoz100057`
pos_hint: {"center_x": .85, "center_y":.1}
大小提示:0.2,0.1
画布之前:
颜色:
RGBA: (0, 0, 0, 1)
圆角矩形:
半径:[20,]
pos: self.pos
尺寸:self.size
MD标签:
pos_hint: {"center_x": .5, "center_y": .5}
文本:root.label
theme_text_color: "自定义"
文本颜色:1、1、1、1
粗体:真
size_hint_x: 1
对齐:“中心”
#...
Working with the Statuses and Generating Thumbnails with OpenCV
The ImageTile is the class for the images and you will use that for all the images and add them to a Grid layout class having just two columns. The fullscreen icon button allows the user to navigate to the fullscreen mode where he can interact with the images. Meanwhile, in your main python code, don't forget to add the ImageFullscreen() function which takes in an argument of the source of the image tile. When creating your ImageTile class in the python code, you will need to add the label attribute so you can use it in the black rounded rectangle aligned to the top right of the image tile. The same principles apply to the VideoTile, just that the label is placed at the bottom right corner of the tile and the play icon button is placed centrally.
#... 类 ImageTile(SmartTileWithLabel, MDFloatLayout): label u003d StringProperty("")
类 VideoTile(SmartTileWithLabel, MDFloatLayout): label u003d StringProperty("")
类 WaSS(MDApp): def **init**(self, **kwargs): super().**init**(**kwargs) self.screen u003d Builder.load\_string(KV) def build(self ): return self.screen def ImageFullscreen(self, current\_source): pass
#...
Now, you have to add a global function - the generate_thumbnail() function. OpenCV is used to generate the thumbnail from the filename of the video and this would save it in the WSS/Thumbnails directory you will create in your build() function. This function will be added outside the main app class, preferably above it.
#...
def generate_thumbnail(video_fn):
output_fn = video_fn[:-4]+".png"
vcap = cv2.VideoCapture(video_fn)
res, im_ar = vcap.read()
while im_ar.mean() < 50 and res:
res, im_ar = vcap.read()
im_ar = cv2.resize(im_ar, (1000, 600), 0, 0, cv2.INTER_LINEAR)
cv2.imwrite("WSS/Thumbnails/"+output_fn, im_ar)
return output_fn
#... In your main app class
def build(self):
path = "/storage/emulated/0/WhatsApp/Media/.Statuses/*.jpg"
if not os.path.exists("WSS"): # "/storage/emulated/0/WSS"
os.makedirs("WSS/Images")
os.makedirs("WSS/Videos")
os.makedirs("WSS/Thumbnails")
#...
我假设您正在使用 Windows/MAC 计算机,因为该应用程序尚未部署,这就是为什么您需要更改您在代码中提供的一些路径的原因。另外,请记住在部署应用程序之前删除Window.size = (360, 640)代码。path变量存储 WhatsApp 在您的 Android 手机上保存的故事的路径。 jpg 之前的星号是您在使用glob模块时用于选择具有该扩展名的所有图像的一种方式。
在您的构建函数中,您将为应用程序编写代码以在创建窗口后立即运行。由于您不想让用户等待图像和视频加载,因此您将遍历所有图像和视频,并将它们附加到您将在声明应用程序类后立即创建的三个不同列表中。由于generate_thumnail()函数返回生成的缩略图的文件名,因此您还将文件名附加到fs_vid_thumbnails列表中。这是从主应用程序类到构建函数的代码:
#...
class WaSS(MDApp):
fs_imgs = []
fs_vids = []
fs_vid_thumbnails = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.screen = Builder.load_string(kv)
def build(self):
path = "/storage/emulated/0/WhatsApp/Media/.Statuses/*.jpg"
if not os.path.exists("WSS"): # "storage/emulated/0/WSS"
os.makedirs("WSS/Images")
os.makedirs("WSS/Videos")
os.makedirs("WSS/Thumbnails")
for i, file in enumerate(glob.glob("*.jpg")):
self.fs_imgs.append(file)
for i, file in enumerate(glob.glob("*.mp4")):
self.fs_vid_thumbnails.append(generate_thumbnail(file))
self.fs_vids.append(file)
#...
构建主屏幕以显示图像状态
主屏幕是应用程序运行后向用户显示的第一个屏幕。为屏幕管理器提供一个 id 始终是一个好习惯,这样您就可以从 KV 代码或主应用程序类的任何部分访问它。有一个工具栏显示您的应用程序的标题和一个“图像”和“视频”选项卡的容器,这两个小部件(MDToolbar和MDTabs)已添加到堆栈布局容器下,因此MDToobar正好放置在顶部MDTabs,很好。
KV = """
#...
ScreenManager:
id: sm
transition: NoTransition()
Screen:
name: "home"
MDStackLayout:
MDToolbar:
title: "[b]WhatsApp Status Saver[/b]"
pos_hint: {"top": 1}
size_hint_y: None
markup: True
md_bg_color: .0274509803921569, .3686274509803922, .3294117647058824, 1
MDTabs:
on_tab_switch: app.on_tab_switch(*args)
valign: "center"
pos_hint_y: None
indicator_color: (1, 1, 1, 1)
tab_hint_x: True
allow_stretch: True
tab_indicator_height: '5dp'
background_color: .0274509803921569, .3686274509803922, .3294117647058824, 1
Tab:
text: "[b]Images[/b]"
markup: True
Tab:
text: "[b]Videos[/b]"
markup: True
"""
#...
在您的主要 python 代码中,您必须创建Tab类,以便在运行代码时不会出现工厂错误。on_tab_switch()是MDTabs类在用户切换选项卡时调用的函数,这将返回 None ,因为您在此应用程序中不需要它。
#...
KV = """
#...
<Tab@MDFloatLayout+MDTabsBase>
ScreenManager:
id: sm
transition: NoTransition()
#...
"""
class WaSS(MDApp):
#...
def on_tab_switch(
self, instance_tabs, instance_tab, instance_tab_label, tab_text
):
return None
#...
现在,您需要在选项卡下添加小部件。对于“图像”选项卡,您将需要一个ScrollView小部件,以便用户可以垂直滚动屏幕。还需要在滚动视图下添加一个GridLayout小部件以包含图像图块。网格布局有两列,行数将由 .Statuses 文件夹中的图像数量决定。还需要将adaptive_height设置为True以便图像可以滚动。对于“视频”选项卡,您将添加与“图像”选项卡相同的小部件。唯一的区别是两个选项卡中每个网格布局的id。
#...
KV = """
#...
MDTabs:
on_tab_switch: app.on_tab_switch(*args)
valign: "center"
pos_hint_y: None
indicator_color: (1, 1, 1, 1)
tab_hint_x: True
allow_stretch: True
tab_indicator_height: '5dp'
background_color: .0274509803921569, .3686274509803922, .3294117647058824, 1
Tab:
text: "[b]Images[/b]"
markup: True
ScrollView:
MDGridLayout:
id: con
adaptive_height: True
spacing: 5
cols: 2
padding: 5
md_bg_color: 1, 1, 1, 1
Tab:
text: "[b]Videos[/b]"
markup: True
ScrollView:
MDGridLayout:
id: vcon
adaptive_height: True
spacing: 5
cols: 2
padding: 5
md_bg_color: 1, 1, 1, 1
#...
"""
#...
为 WhatsApp 状态添加图像和视频
是时候开始为用户显示一些内容了,您将包含代码以在相应的网格布局类下为图像和视频添加图块,并通过它们的 ids 引用它们。您只需要更新 build() 代码块并使用循环添加图块,以选择用户可以看到并与之交互的每个图像和缩略图的来源。 Python 的 enumerate() 函数允许您获取文件名及其索引。您需要索引来获取图块的位置,以便可以在 label 属性中使用它们。由于 Python 中的索引从 0 开始,因此您必须将每个索引加 1,并酌情包括结果和 fs_imgs 或 fs_vid_thumbnails 的长度。
#...
def build(self):
path = "/storage/emulated/0/WhatsApp/Media/.Statuses/*.jpg"
if not os.path.exists("WSS"): # "storage/emulated/0/WSS"
os.makedirs("WSS/Images")
os.makedirs("WSS/Videos")
os.makedirs("WSS/Thumbnails")
for i, file in enumerate(glob.glob("*.jpg")):
self.fs_imgs.append(file)
for i, file in enumerate(glob.glob("*.mp4")):
self.fs_vid_thumbnails.append(generate_thumbnail(file))
self.fs_vids.append(file)
for i, file in enumerate(self.fs_imgs):
self.screen.ids.con.rows = i
imageTile = ImageTile(
source= file,
size_hint= (1, None),
size= (100, 400),
label=f"{i+1}/{len(self.fs_imgs)}",
)
self.screen.ids.con.add_widget(imageTile)
for i, thumbnail in enumerate(self.fs_vid_thumbnails):
self.screen.ids.vcon.rows = i
videoTile = VideoTile(
source= thumbnail,
size_hint=(1, None),
size=(100, 400),
label=f"{i+1}/{len(self.fs_vid_thumbnails)}",
)
self.screen.ids.vcon.add_widget(videoTile)
## print(self.screen.ids.carousel.current_slide)
return self.screen
#...
完成图像
由于用户可以单击全屏图标按钮以在单独的屏幕中显示相应的图像,用户可以在其中保存它,向左或向右滑动,然后导航回主屏幕,因此您需要使用模板中的第二个屏幕并为全屏模式创建一个单独的屏幕。但是,您必须更新ImageFullscreen功能代码块以将所有图像添加到MDCarousel小部件,该小部件是控制用户向左或向右滑动动作的容器。
#...
KV = """
#...
Screen:
name: "imgfs"
MDFloatLayout:
id: imcon
orientation: "vertical"
md_bg_color: 0, 0, 0, 1
MDStackLayout:
MDToolbar:
title: ""
pos_hint: {"top": 1}
size_hint_y: None
markup: True
left_action_items: [["arrow-left", lambda x: app.back()]]
md_bg_color: .0274509803921569, .3686274509803922, .3294117647058824, 1
MDCarousel:
id: carousel
MDFloatingActionButton:
icon: "content-save-outline"
md_bg_color: .1450980392156863, .8274509803921569, .4, 1
pos_hint: {"center_x": .9, "center_y": .1}
on_release:
app.save_image()
#...
"""
#...
def ImageFullscreen(self, current_source):
for source in self.fs_imgs:
ft = FitImage(
source=source,
pos_hint= {"center_x":.5, "center_y":.5},
size_hint= (1, 1))
self.screen.ids["img"] = ft
self.screen.ids.carousel.add_widget(ft)
current_index = self.fs_imgs.index(current_source)
self.screen.ids.carousel.index = current_index
self.screen.current = "imgfs"
#...
工具栏中的后退按钮通过使用主应用程序类中的函数导航回主屏幕。您将创建back()函数,因为后退按钮的on_release()事件只能通过使用 lambda 函数来控制。如果您不想编写back()函数,则必须创建自定义工具栏并添加图标按钮类;我敢肯定你不想再考虑一下。要保存图像,您将使用datetime和time模块来获取当前日期和时间。 按下保存按钮时将保存全屏上的活动图像,这将调用 save_image() 函数,该函数使用轮播的当前索引并从图像列表中选择相应的图像 (fs_imgs)你之前创建的。你通过向用户显示一条 toast 消息来让事情变得更好,说明图像已被保存。strftime()方法允许您格式化时间,并使用下划线分隔数字。
#...
def back(self):
self.screen.current = "home"
def save_image(self):
#print(self.screen.ids.carousel.index)
dnow, c_time = date.today(), time.strftime("%H_%M_%S")
c_date = dnow.strftime("%m_%d_%y")
save_as_fn = f"WSS_IMG_{c_date}_{c_time}.png"
shutil.copy(self.fs_imgs[self.screen.ids.carousel.index], f"WSS/Images/{save_as_fn}")
toast("Image saved!")
#...
处理视频
视频的全屏有很多东西。您拥有包含返回和保存按钮、控制栏和图标的不同功能的工具栏。这里的后退键的功能与图像屏幕的功能不同,您必须考虑视频的状态是否正在播放。如果它正在播放,您需要在导航到主屏幕之前停止它,这样它就不会在后台播放。保存按钮的功能几乎完成了图像功能的所有功能,唯一的区别是文件名的模式和 toast 消息显示视频已保存,而不是说图像。使用模板中的第三个屏幕为视频创建屏幕。在这里,您使用浮动布局而不是轮播,因为用户不需要向左或向右滑动,因为控制栏已经有上一个和下一个图标按钮。
#...
"""
#...Include this in your KV code, immediately after the image full screen block
Screen:
name: "vidfs"
MDFloatLayout:
id: vicon
orientation: "vertical"
md_bg_color: 0, 0, 0, 1
MDStackLayout:
spacing: 25
MDToolbar:
title: ""
pos_hint: {"top": 1}
size_hint_y: None
markup: True
left_action_items: [["arrow-left", lambda x: app.back_from_vid()]]
right_action_items: [["content-save", lambda x: app.save_video()]]
md_bg_color: .0274509803921569, .3686274509803922, .3294117647058824, 1
MDFloatLayout:
id: vplayercon
orientation: "vertical"
pos_hint: {"center_x": .5, "center_y":.6}
"""
#...
#...
def save_video(self):
dnow, c_time = date.today(), time.strftime("%H_%M_%S")
c_date = dnow.strftime("%m_%d_%y")
save_as_fn = f"WSS_VID_{c_date}_{c_time}.mp4"
shutil.copy(self.screen.ids.vid.source, f"WSS/Videos/{save_as_fn}")
toast("Video saved!")
def back_from_vid(self):
if self.screen.ids.vid.state == "play":
self.screen.ids.vid.state = "stop"
self.screen.current = "home"
def VideoFullscreen(self, current_source):
self.screen.ids.playpause.icon = "play"
ft = Video(
source=self.fs_vids[self.fs_vid_thumbnails.index(current_source[:-4]+".mp4")],
state= "stop",
pos_hint= {"center_x": .5, "center_y": .55},
options= {"allow_stretch": True},
)
ft.bind(position=self.on_position_change, duration=self.on_duration_change)
self.screen.ids["vid"] = ft
self.screen.ids.vplayercon.clear_widgets()
self.screen.ids.vplayercon.add_widget(ft)
self.screen.current = "vidfs"
#...
如果你注意到了什么,Video类的实例的源代码是从缩略图中派生的。这是因为缩略图和视频具有相同的名称,但扩展名除外。这就是为什么我从fs_vid_thumbnails列表中选择了不带扩展名 (png) 的文件名并添加了 mp4 扩展名。
构建与视频状态交互的控制栏
控制栏允许用户与当前正在播放的视频进行交互。视频的当前位置和视频的持续时间有两个标签,然后是一个滑块,允许用户搜索,从该位置播放视频。
"""
#...KV
MDBoxLayout:
orientation: "vertical"
pos_hint: {"center_x": .5, "bottom": 1}
size_hint_y: .3
spacing: 10
margin: 10
canvas.before:
Color:
rgba: 0, 0, 0, .5
Rectangle:
pos: self.pos
size: self.size
MDBoxLayout:
orientation: "horizontal"
pos_hint: {"center_x":.5, "center_y":.75}
MDLabel:
id: start
text: "0:00"
font_size: 14
markup: True
# bold: True
font_name: "DejaVuSans.ttf"
halign: "center"
padding: [5,0]
theme_text_color: "Custom"
text_color: 1, 1, 1, 1
pos_hint: {"center_y":.5}
size_hint_x: 1
MDSlider:
id:seek
min: 0
value: 0
max: 0
hint: False
size_hint: 1, None
halign: "center"
valign: "center"
pos_hint: {"center_x":.5, "center_y":.5}
color: .9, .3, .3, 1
on_active:
app.update_seek()
MDLabel:
id: end
text: "0:00"
font_size: 14
markup: True
# bold: True
halign: "center"
font_name: "DejaVuSans.ttf"
padding: [5,0]
theme_text_color: "Custom"
text_color: 1, 1, 1, 1
pos_hint: {"center_y":.5}
size_hint_x: 1
#...
"""
on_position_change()函数控制视频的搜索。 Kivy 在加载视频时给出的默认位置是 -1,这会立即更改视频正在播放,因此您必须以秒为单位将位置转换为分钟和秒。然后,您将使用结果更新当前位置的第一个标签。另一方面,on_duration_change()是一个在视频开始播放时调用的函数。此外,持续时间的默认值为 -1,它会在视频开始播放后立即更改。但是,在用户播放视频之前,持续时间的标签会显示 1;避免这种情况的一种方法是使用一个条件,即只有在持续时间不是 -1 时才应更新持续时间标签。
#...Under your main app class
def on_position_change(self, instance, value):
# convert to minutes:seconds
d = self.screen.ids.vid.position
minutes = int(d / 60)
seconds = int(d - (minutes * 60))
# fix label & position
self.screen.ids.start.text = '%d:%02d' % (minutes, seconds)
self.screen.ids.seek.value = value
def on_duration_change(self, instance, value):
# convert to minutes:seconds
d = self.screen.ids.vid.duration
mins = int(d / 60)
secs = int(d - (mins * 60))
if secs != 1:
self.screen.ids.end.text = f"{mins}:{secs}"
self.screen.ids.seek.min = 0
self.screen.ids.seek.max = self.screen.ids.vid.duration
#...
您将创建一个update_seek()函数来更改视频的位置。搜索时,您将使用Video类中的seek()方法并传入搜索百分比的参数,即所需位置与视频持续时间的比率。
#...In your main app class
def update_seek(self):
## print(self.screen.ids.seek.value)
self.screen.ids.vid.seek(self.screen.ids.seek.value / self.screen.ids.vid.duration)
#...
添加播放、上一个和下一个按钮
这三个重要的按钮使用户可以随意控制视频。播放按钮在播放图标和暂停图标之间切换,当视频尚未播放或视频已暂停时显示播放图标,而在视频播放时显示暂停图标。
#... KV Code, under the video fullscreen
"""
MDFloatLayout:
spacing: 50
padding: 20
pos_hint: {"center_x": .5}
MDIconButton:
icon: "skip-previous"
theme_text_color: "Custom"
text_color: (1, 1, 1, 1)
pos_hint: {"center_x": .25, "center_y":.5}
on_release:
app.play_previous(root.ids.vid.source)
MDFloatingActionButton:
id: playpause
icon: "play"
pos_hint: {"center_y":.5, "center_x":.5}
theme_text_color: "Custom"
text_color: 1, 1, 1, 1
md_bg_color: .9, .3, .3, 1
elevation: 0
user_font_size: "30sp"
on_release:
app.playpause()
MDIconButton:
icon: "skip-next"
theme_text_color: "Custom"
text_color: (1, 1, 1, 1)
pos_hint: {"center_x": .75, "center_y":.5}
on_release:
app.play_next(root.ids.vid.source)
# The last snippet in your KV code
"""
#...
最后,显然需要使按钮按其用途工作。当用户导航到视频全屏时,播放按钮会显示播放图标,并在单击时相应更改。这个播放按钮通过调用一个函数来控制视频,如果视频当前正在播放,则暂停视频;如果视频尚未播放或视频已暂停,则播放视频,每次点击后图标都会更新。您还需要编写三个函数,并且您的应用程序已准备就绪。第一个功能用于播放按钮,第二个功能用于上一个按钮,最后一个功能用于下一个按钮。
#...In your main app class
def playpause(self):
self.screen.ids.vid.state = "play" if self.screen.ids.vid.state == "pause" or self.screen.ids.vid.state == "stop" else "pause"
self.screen.ids.playpause.icon = "pause" if self.screen.ids.vid.state == "play" else "play"
def play_previous(self, current_src):
current_index = self.fs_vids.index(current_src)
if current_index == 0:
return None
else:
self.screen.ids.vid.source = self.fs_vids[current_index-1]
def play_next(self, current_src):
current_index = self.fs_vids.index(current_src)
if current_index == len(self.fs_vids)-1:
return None
else:
self.screen.ids.vid.source = self.fs_vids[current_index+1]
#...
play_previous()函数接受视频播放源的参数,如果当前播放的视频是列表中的第一个,则返回 None。否则,该函数使用fs_vids列表来获取当前正在播放的视频的文件名,并在列表中选择它之前的文件名元素。所选文件名现在成为Video类的当前源。 'play_next()` 函数具有类似的原理。如果当前播放的视频是最后一个,则返回 None,但从同一列表中获取下一个文件名,并在当前视频不是最后一个时使其成为当前源。
现在呢?
您的 WhatsApp 状态保护应用程序现已准备好部署,您可以在此处找到完整代码。在部署之前不要忘记删除窗口大小的代码,并将opencv-python等特殊库包含在dependencies中的buildozer.spec文件中。您可以使用buildozer或python_for_android或Kivy Launcher 应用程序来部署应用程序。以下是我电脑上的一些屏幕截图,您的应用应该看起来类似:






更多推荐

所有评论(0)