我用 Kivy 制作了一个相对简单的应用程序,根目录包含一个“音频”文件夹,其中有两个子文件夹,每个子文件夹包含一些 .wav 文件。该应用程序在我的桌面上运行良好,但一旦部署到我的 Android 上,logcat 就会给我一个与 .wav 文件相关的 FileNotFoundError。
我已将“wav”作为包含的扩展包含在 buildozer.spec中:
#(list) Source files to include (let empty to include all the files)
source.include_exts. = py,png,jpg,kv,atlas,wav
我相信我也在这里正确包含了文件夹:
#(list) List of inclusions using pattern matching
source.include_patterns = audio/guard/*.wav,audio/chisau/*.wav
整个部署过程进展顺利,直到应用程序应该运行。 Kivy 徽标出现后,应用程序将关闭。
这是我的 logcat 的相应片段:
https://www.pygame.org/contribute.html
03-17 17:49:25.967 15383 15437 I python : Traceback (most recent call last):
03-17 17:49:25.967 15383 15437 I python : File "/home/luke/WingChun-Assistant/.buildozer/android/app/main.py", line 51, in <module>
03-17 17:49:25.968 15383 15437 I python : FileNotFoundError: No such file or directory.
03-17 17:49:25.968 15383 15437 I python : Python for android ended.
这是我的 main.py:
import kivy
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.popup import Popup
from kivy.clock import Clock
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.uix.togglebutton import ToggleButton
from kivy.core.audio import SoundLoader
from kivy.core.window import Window
Window.clearcolor = (1, 1, 1, 1)
import time, sys, random
from threading import Thread
import pygame
from pygame import mixer
pygame.init()
#################################### Variables and Containers ####################
# minimum cooldown between redirections
cooldown = 2
global session_time
session_time = 0
# change of guard redirections
cog_redirections = {"1": "Tan_sau", "2": "Bong_sau", "3": "Kau_sau", "4": "Low_Garn", "5": "Garn_sau",
"6": "Kwun_sau", "7": "Lap_sau", "8": "Pak_sau", "9": "Gum_sau", "10": "Biu_sau"}
# chi sau redirections
cs_redirections = {"1": "Tan", "2": "Bong", "3": "Gum", "4": "Garn", "5": "Kwun", "6": "Pak"}
# session redirections
global session_list
session_list = []
global function_stop
function_stop = False
######################################### AUDIO ##################################
# change of guard redirections
tan_sau = mixer.Sound("audio/guard/Tan_sau.wav")
bong_sau = mixer.Sound("audio/guard/Bong_sau.wav")
kau_sau = mixer.Sound("audio/guard/Kau_sau.wav")
low_garn = mixer.Sound("audio/guard/Low_Garn.wav")
garn_sau = mixer.Sound("audio/guard/Garn_sau.wav")
kwun_sau = mixer.Sound("audio/guard/Kwun_sau.wav")
lap_sau = mixer.Sound("audio/guard/Lap_sau.wav")
pak_sau = mixer.Sound("audio/guard/Pak_sau.wav")
biu_sau = mixer.Sound("audio/guard/Biu_sau.wav")
gum_sau = mixer.Sound("audio/guard/Gum_sau.wav")
# chi sau redirections
tan = mixer.Sound("audio/chisau/Tan.wav")
bong = mixer.Sound("audio/chisau/Bong.wav")
gum = mixer.Sound("audio/chisau/Gum.wav")
garn = mixer.Sound("audio/chisau/Garn.wav")
kwun = mixer.Sound("audio/chisau/Kwun.wav")
pak = mixer.Sound("audio/chisau/Pak.wav")
cog_dict = {"Tan_sau": tan_sau, "Bong_sau": bong_sau, "Kau_sau": kau_sau, "Low_Garn": low_garn,
"Garn_sau": garn_sau, "Kwun_sau": kwun_sau, "Lap_sau": lap_sau, "Pak_sau": pak_sau,
"Gum_sau": gum_sau, "Biu_sau": biu_sau, "Tan": tan, "Bong": bong, "Gum": gum,
"Garn": garn, "Kwun": kwun, "Pak": pak}
global step
step = 1
################################ Buttons and Layouts ###################################
class Widgets(Widget):
pass
class MainMenu(Screen): # Main Menu
def red_notes(self):
RedirectionsNotes().open()
def chisau_notes(self):
ChisauNotes().open()
class CSMenu(Screen): # Chi Sau Menu
def btn(self):
show_popup()
def btnt2(self):
global session_list
session_list = []
show_popupt2()
class COGMenu(Screen): # Change of Guard Menu
def btn(self):
show_popup()
def btnt(self):
global session_list
session_list = []
show_popupt()
class InSessionMenu(Screen): # Further Training Session Menu
def stop_function(self):
global function_stop
function_stop = True
class COGSessionMenu(Screen): # Training Session Menu
def button_press(self):
# create the thread to invoke other_func with arguments (2, 5)
t = Thread(target=self.in_session)
# set daemon to true so the thread dies when app is closed
t.daemon = True
# start the thread
t.start()
def progress(self):
# create the thread to invoke other_func with arguments (2, 5)
t = Thread(target=self.in_progress)
# set daemon to true so the thread dies when app is closed
t.daemon = True
# start the thread
t.start()
def in_progress(self):
global step
time.sleep(6)
while function_stop == False:
if step == 1:
Pr1().open()
time.sleep(1)
step += 1
elif step == 2:
Pr2().open()
time.sleep(1)
step += 1
elif step == 3:
Pr3().open()
time.sleep(1)
step = 1
def play_tech(self, new_dict):
technique = (random.choice(session_list))
print(technique)
new_dict[technique].play()
def in_session(self):
global function_stop
function_stop = False
Gr5().open()
time.sleep(1)
Gr4().open()
time.sleep(1)
Gr3().open()
time.sleep(1)
Gr2().open()
time.sleep(1)
Gr1().open()
time.sleep(1)
Gr().open()
now = time.time()
timer = 0
while timer < session_length and function_stop == False:
self.play_tech(cog_dict)
end = time.time()
timer = round(end - now)
print(timer)
time.sleep((cooldown + random.randint(0, 4)))
return
def stop_function(self):
global function_stop
function_stop = True
class DiffMenu(Screen):
pass
class WindowManager(ScreenManager):
pass
kv = Builder.load_file("my.kv")
################## Popups #########################
################## Progress Popups #################
class Pr1(Popup):
def __init__(self, **kwargs):
super(Pr1, self).__init__(**kwargs)
Clock.schedule_once(self.dismiss_popup, 1)
def dismiss_popup(self, dt):
self.dismiss()
class Pr2(Popup):
def __init__(self, **kwargs):
super(Pr2, self).__init__(**kwargs)
Clock.schedule_once(self.dismiss_popup, 1)
def dismiss_popup(self, dt):
self.dismiss()
class Pr3(Popup):
def __init__(self, **kwargs):
super(Pr3, self).__init__(**kwargs)
Clock.schedule_once(self.dismiss_popup, 1)
def dismiss_popup(self, dt):
self.dismiss()
################## Notes Popups ######################
class RedirectionsNotes(Popup):
def __init__(self, **kwargs):
super(RedirectionsNotes, self).__init__(**kwargs)
def dismiss_popup(self):
self.dismiss()
class ChisauNotes(Popup):
def __init__(self, **kwargs):
super(ChisauNotes, self).__init__(**kwargs)
def dismiss_popup(self):
self.dismiss()
################ Get Ready Popups ############
class Gr5(Popup):
def __init__(self, **kwargs):
super(Gr5, self).__init__(**kwargs)
Clock.schedule_once(self.dismiss_popup, 1)
def dismiss_popup(self, dt):
self.dismiss()
class Gr4(Popup):
def __init__(self, **kwargs):
super(Gr4, self).__init__(**kwargs)
Clock.schedule_once(self.dismiss_popup, 1)
def dismiss_popup(self, dt):
self.dismiss()
class Gr3(Popup):
def __init__(self, **kwargs):
super(Gr3, self).__init__(**kwargs)
Clock.schedule_once(self.dismiss_popup, 1)
def dismiss_popup(self, dt):
self.dismiss()
class Gr2(Popup):
def __init__(self, **kwargs):
super(Gr2, self).__init__(**kwargs)
Clock.schedule_once(self.dismiss_popup, 1)
def dismiss_popup(self, dt):
self.dismiss()
class Gr1(Popup):
def __init__(self, **kwargs):
super(Gr1, self).__init__(**kwargs)
Clock.schedule_once(self.dismiss_popup, 1)
def dismiss_popup(self, dt):
self.dismiss()
class Gr(Popup):
def __init__(self, **kwargs):
super(Gr, self).__init__(**kwargs)
Clock.schedule_once(self.dismiss_popup, 1)
def dismiss_popup(self, dt):
self.dismiss()
################ Session Length Menu ###############
class P(FloatLayout):
def session_time_60(self):
global session_length
session_length = 60
print(session_length)
def session_time_120(self):
global session_length
session_length = 120
print(session_length)
def session_time_300(self):
global session_length
session_length = 300
print(session_length)
def session_time_600(self):
global session_length
session_length = 600
print(session_length)
################ Change of Guard Techniques Menu ##########
class Pt(FloatLayout):
def populate_pt(self, technique):
if technique in session_list:
session_list.remove(technique)
print(session_list)
else:
session_list.append(technique)
print(session_list)
################ Chi Sau Techniques Menu #################
class Pt2(FloatLayout):
def populate_pt2(self, technique):
if technique in session_list:
session_list.remove(technique)
print(session_list)
else:
session_list.append(technique)
print(session_list)
class Wing_Chun_Training_AssistantApp(App):
def build(self):
return kv
##################################### Functions #############################################
def show_popup():
show = P()
popupWindow = Popup(background_color = [1, 0.0, 0.0, 1], separator_color = [0.9, 0.0, 0.0, 1], title = "Choose Length of Session, then click outside to close", content = show, size_hint = (0.8, 0.8), size = (400, 400))
popupWindow.open()
def show_popupt():
showt = Pt()
popupWindowt = Popup(background_color = [1, 0.0, 0.0, 1], separator_color = [0.9, 0.0, 0.0, 1], title = "Select Techniques to Train, then click outside to close", content = showt, size_hint = (0.8, 0.8), size = (400, 400))
popupWindowt.open()
def show_popupt2():
showt2 = Pt2()
popupWindowt2 = Popup(background_color = [1, 0.0, 0.0, 1], separator_color = [0.9, 0.0, 0.0, 1], title = "Select Techniques to Train, then click outside to close", content = showt2, size_hint = (0.8, 0.8), size = (400, 300))
popupWindowt2.open()
###################################### Run Loop ##############################################
if __name__ == "__main__":
Wing_Chun_Training_AssistantApp().run()
tan_sau = mixer.Sound("audio/guard/Tan_sau.wav")
线,是51号线
除此之外,我的根文件夹还包含我的 main.py 文件、.kv 文件、背景 .png 文件和音频文件夹。音频文件夹包含两个子文件夹,其中包含 wav 文件。
我已经提取了 APK 文件的内容,但任何地方都没有 wav 的迹象。我做错了什么?
非常感谢您提供的任何帮助。
编辑:
audio 文件夹以及两个子文件夹和 wav 文件确实位于 apk 文件中。然而我仍然遇到同样的错误。
也许尝试使用 Kivy 自己的 SoundLoader 函数来播放音频文件。像这样:
from kivy.core.audio import SoundLoader
sound = SoundLoader.load('mytest.wav')
if sound:
print("Sound found at %s" % sound.source)
print("Sound is %.3f seconds" % sound.length)
sound.play()
更多信息可以在这里找到。
另外,尝试使用
os.path.join
,如 @NameKhan72 在他的答案中提到的。不要忘记先import os
。在再次重建 apk 之前清理 Buildozer 文件夹(在您的项目文件夹中)。
别介意我原来的答案。我发现太晚了,我错了。
我做了一些研究,显然 Buildozer 可以求助于构建的缓存版本,而不是在编译之前重建项目。尝试删除隐藏的 buildozer 文件夹并重建项目。
编辑1:
尝试通过此方法使用文件的绝对路径:
root_folder = self.user_data_dir # only works in the App class, so make it a global variable
那么你可以这样做:
tan_sau_path = os.path.join(root_folder, 'audio/guard/Tan_sau.wav')
tan_sau = mixer.Sound(tan_sau_path)
来自您计划使用声音的班级。
长话短说,缺少 buildozer.spec 中路径的第一部分:
#(list) 使用模式匹配的包含列表 source.include_patterns = %(source.dir)s/audio/*
这将包括“音频”内的任何子文件夹以及带有任何扩展名的任何文件。包含在“source.include_exts =”中<--- No dot (.)