Tkinter stuck in GUI while playing mp3 file - python

I'm a complete beginner in Python and currently making a GUI with Tkinter that can play mp3 files. just for practice.
I'm using a Mac, and the rainbow wheel that appears when a program lags shows up when I press the play button I made. And it doesn't let me press any buttons while the mp3 file is playing.
Can anybody help me figure this out please?
from pydub import AudioSegment
from pydub.playback import play
from tkinter import *
class MP3:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.go_back_button = Button(frame, text = '<<')
self.go_back_button.grid(row = 0 , column = 0)
self.play_button = Button(frame, text = '|>', command = self.play_song)
self.play_button.grid(row = 0 , column = 1)
self.pause_button = Button(frame, text = '||', command = self.pause_song)
self.pause_button.grid(row = 0 , column = 2)
self.go_forward_button = Button(frame, text = '>>')
self.go_forward_button.grid(row = 0 , column = 3)
self.shuffle_button = Button(frame, text = 'SHUFFLE')
self.shuffle_button.grid(row = 0 , column = 4)
self.is_paused = False
self.song_list = ['songs.mp3']
self.i = 0
def play_song(self):
while self.is_paused is False:
song = AudioSegment.from_mp3("/Users/bang/Desktop/music/{}".format(self.song_list[self.i]))
play(song)
def pause_song(self):
self.is_paused = True
root = Tk()
myMp3 = MP3(root)
root.mainloop()

I recommend using simpleaudio (e.g. pip install simpleaudio) like suggested in the pydub readme
If you use pydub.playback.play() it will still wait for the playback to finish, but pydub.playback._play_with_simpleaudio() will run in a thread and not block the interpreter.
You'll probably want to use the _play_with_simpleaudio() function as a starting point for your own playback functionality which uses simpleaudio directly

Related

Timer stops updating when an audio is played

I have used after() method to update the time remaining for quiz and converted text of question to audio using gtts module and played that using playsound module. But when the audio is played timer stops updating. How can I fix it?
import playsound
import tkinter
import gtts
import os
def speak_que():
global audio_no
sound = gtts.gTTS(question_label["text"], lang = "en")
file_name = "Audio_" + str(audio_no) + ".mp3"
sound.save(file_name)
playsound.playsound(file_name)
os.remove(file_name)
audio_no += 1
def change_time():
pre_time = int(time_label["text"])
if pre_time != 1:
time_label.config(text = pre_time-1)
time_label.after(1000, change_time)
else:
window.destroy()
window = tkinter.Tk()
audio_no = 0
time_label = tkinter.Label(window, text = "15")
time_label.after(1000, change_time)
question_label = tkinter.Label(window, text = "What is the sum of 4 and 2")
answer = tkinter.Entry(window)
speak = tkinter.Button(window, text = "Speak", command = speak_que)
time_label.pack()
question_label.pack()
answer.pack()
speak.pack()
window.mainloop()
First, make sure your speak_que() routine is completing, if not, you can install the older version of playsound 1.2.2 as the newest version tends to have issues.
pip uninstall playsound
pip install playsound==1.2.2
Next, if you want the timer to continue during speak_que() (or any two operations in parallel); you'll have to use threading, see below.
import playsound
import tkinter
import gtts
import os
from threading import *
def speak_que():
global audio_no
sound = gtts.gTTS(question_label["text"], lang = "en")
file_name = "Audio_" + str(audio_no) + ".mp3"
sound.save(file_name)
playsound.playsound(file_name)
os.remove(file_name)
audio_no += 1
def threadedSound():
t1=Thread(target=speak_que)
t1.start()
def change_time():
pre_time = int(time_label["text"])
if pre_time != 1:
time_label.config(text = pre_time-1)
time_label.after(1000, change_time)
else:
window.destroy()
window = tkinter.Tk()
audio_no = 0
time_label = tkinter.Label(window, text = "15")
time_label.after(1000, change_time)
question_label = tkinter.Label(window, text = "What is the sum of 4 and 2")
answer = tkinter.Entry(window)
speak = tkinter.Button(window, text = "Speak", command = threadedSound)
time_label.pack()
question_label.pack()
answer.pack()
speak.pack()
window.mainloop()

set expiration date for python application

Hello dear programmers,
I have generated an EXE with pyinstaller. However, I would like this EXE to be valid for only 6 months so that users have to re-download a improved EXE with updates from a download-center. Is there any way to add this in the program code?
Unfortunately I can't include a standard update function because the users often don't have internet.
I am curious about your suggestions. Thanks a lot guys! I've attached a small snippet of the program below.
import tkinter as tk
from tkinter import ttk
class Win1:
def __init__(self, master):
self.master = master
self.topFrame = tk.Frame(self.master)
self.topFrame.grid(row=0, column=0, sticky='news', ipady = 5)
self.B_GapFrame = tk.Frame(self.master)
self.master.resizable(False, False)
self.gapType = tk.StringVar(self.master)
self.choiceGap = ['RBC, TUR']
self.gapType.set('') # set the default option
self.ctngMenu = tk.OptionMenu(self.topFrame, self.gapType, *self.choiceGap, command=self.choose_gap_handle)
self.ctngMenu.config(width=40)
self.LabelGap = tk.Label(self.topFrame, text="Select TYPE")
self.LabelGap.grid(row = 3, column = 0,sticky = "W")
self.ctngMenu.grid(row = 3, column =2,sticky = "W", columnspan = 3)
self.frameVar = tk.StringVar(self.master)
self.choiceFrame = "Test"
self.frameVar.set('') # set the default option
self.frameMenu = ttk.Combobox(self.topFrame, values= self.choiceFrame, state = "readonly", justify = "center", textvariable = self.frameVar, width = 12)
self.frameMenu.grid(row = 1, column =2,sticky = "W", pady = 7, columnspan = 3)
self.LabelFrame = tk.Label(self.topFrame, text="Select TUR ")
self.LabelFrame.grid(row = 1, column = 0,sticky = "W",pady =7)
def choose_gap_handle(self, selected_Gap):
if selected_Gap == 'RBC, TUR':
self.B_GapFrame.tkraise()
self.B_GapFrame.grid(row=2, column=0, sticky='news')
root = tk.Tk()
root.geometry("+50+50")
app = Win1(root)
root.mainloop()
It all depends how 'cruel' you want to be.
One option might be to add a hard coded date in your code like
import datetime
RLS_DATE = datetime.date(2021, 02, 24)
and then before your main code section something like:
if datetime.date.today() - RLS_DATE > 7 * 30: # about 7 months
# as #TheLizzard suggested delete the executable.
# You might do this only if running under
# py2exe to avoid deleting your own source code
if getattr(sys, 'frozen', False):
os.remove(sys.argv[0])
elif datetime.date.today() - RLS_DATE > 6 * 30: # about 6 months
# write your own coded, that shows just a popup and lets the user
# continue using the app.
If you don't want to hardcode the release date, you could write a smarter script to create your executable and add some data for py2exe, that contains the date at which you compiled the executable.
What I mean with being cruel.
Either just show a popup, that the user should update,
or add a popup and block the user interface for x seconds.
The delay might increase the longer the user doesn't update
or just exit the program
or even delete the program
Of course all these options are not safe.
This is just for standard users.
If somebody insists to run your program, he can modify the generated executable to ignore the date.
You can save your app first launch time to local file and then compare it with current time.
import os
import time
import tempfile
import tkinter as tk
EXPIRATION_TIME_SEC = 100
class App(object):
def __init__(self):
config = os.path.join(tempfile.gettempdir(), 'config.txt')
self.start_time = time.time()
if os.path.isfile(config):
with open(config, 'r') as f:
self.start_time = int(float(f.read()))
else:
with open(config, 'w') as f:
f.write(f'{self.start_time}')
self.root = tk.Tk()
self.label = tk.Label(self.root)
self.label.pack()
self.update_label()
self.root.mainloop()
def update_label(self):
if time.time() - self.start_time > EXPIRATION_TIME_SEC:
self.root.quit()
self.label.configure(text=f'Expired in: {int(self.start_time+EXPIRATION_TIME_SEC-time.time())} sec.')
self.root.after(1000, self.update_label)
App()
Output:

Program crashes on invoking menu when using threading and StringVar()

I've made a Python program which reads the file and then sends/receives the data towards/from the microcontroller and everything worked well until I added a menu to display short instructions.
Since the UART communication has to run in a separate thread I used threading and StringVar() to access the data from the main thread.
To demonstrate the problem I've made a short example which has nothing to do with microcontrollers.
The steps to reproduce the problem:
Click the Next screen radio button to open the second screen (at the initial screen the menu works well)
Click Help > Instructions
After (or sometimes even before) closing the message box the program will crash with:
TclStackFree: incorrect freePtr. Call out of sequence?
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
Note:
In the original program where there are more UI elements the program always crashes before showing the message box and after removing even more UI elements the program doesn't crash every time - that's why I've left some "redundant" labels. When some more labels are added the program will crash every time.
I have narrowed the cause of the crash to:
displayVal.set()
in checkForData() function. By removing that instruction everything works well.
Moreover, after removing displayVal.trace("w", displayVal_trace) the program will not crash anymore but opening the menu will still temporarily freeze the working thread.
I thought after displayVal.set() Tkinter is trying to update the label but can't because of showing the menu - however, the problem remained even after I removed label_data = Label(up2, textvariable = displayVal).
Here is the stripped down code tested with Python 2.7:
from Tkinter import *
import tkMessageBox
import threading
import time
threadRun = True
checkDelay = 0.5
def checkForData():
global threadRun, checkDelay
print "Simulating thread for receiving messages from UART"
while threadRun == True:
print time.time()
displayVal.set(time.time()) # <-- if removed the menu works OK (no crash)
time.sleep(checkDelay)
print "No more receiving of messages"
def listenForData():
t = threading.Thread(target=checkForData)
t.daemon = False
t.start()
def stopListening():
global threadRun, checkDelay
threadRun = False
time.sleep(checkDelay + 0.1)
def exit_handler():
print "Exiting..."
stopListening()
root.destroy()
root = Tk()
right = int((root.winfo_screenwidth() - 600) / 2)
down = int(root.winfo_screenheight() / 3 - 400 / 2)
root.geometry("600x400+%d+%d" % (right, down))
root.resizable(width = False, height = False)
root.protocol("WM_DELETE_WINDOW", exit_handler)
displayVal = StringVar()
displayVal.set("nothing")
def setupView():
global masterframe
masterframe = Frame()
masterframe.pack(fill=BOTH, expand=True)
selectPort() # the 1st screen for selecting COM ports
def selectPort():
global masterframe, radioVar
# remove everything from the frame
for child in masterframe.winfo_children():
child.destroy()
radioVar = StringVar()
l1 = Label(masterframe, text = "Select...")
l1.pack(pady=(50, 20))
# this would be a list of detected COM ports
lst = ["Next screen"]
if len(lst) > 0:
for n in lst:
r1 = Radiobutton(masterframe, text=n, variable=radioVar, value=n)
r1.config(command = next_screen)
r1.pack()
def mainScreen():
global masterframe, term, status
# remove previous screen from the frame
for child in masterframe.winfo_children():
child.destroy()
up1 = Frame(masterframe)
up1.pack(side=TOP)
up2 = Frame(masterframe)
up2.pack()
terminal = Frame(masterframe)
terminal.pack()
down = Frame(masterframe)
down.pack(side=BOTTOM, fill=BOTH)
label_top = Label(up1, text="Something")
label_top.pack(pady=5)
label_data = Label(up2, textvariable = displayVal)
label_data.pack(pady=(10, 0))
term = Text(terminal, height=10, width=35, bg="white")
term.pack()
term.tag_config("red", foreground="red")
term.tag_config("blue", foreground="blue")
term.insert(END, "The file has been read\n", "red")
term.insert(END, "File contents:\n")
term.insert(END, data)
status = Label(down, text="Status...", bd=1, relief=SUNKEN, anchor=W, bg="green")
status.pack(fill=X)
displayVal.trace("w", displayVal_trace) # <-- if removed only temporary freeze but no crash
def displayVal_trace(name, index, mode):
global term
if(displayVal.get() != "NOTHING"):
term.insert(END, "\nReceived: ", "blue")
term.insert(END, displayVal.get())
term.see(END)
def next_screen():
listenForData()
mainScreen()
def stop():
stopListening()
def instructions():
tkMessageBox.showinfo("Help", "This is help")
main_menu = Menu(root)
root.config(menu = main_menu)
help_menu = Menu(main_menu)
main_menu.add_cascade(label="Help", menu=help_menu)
help_menu.add_command(label="Instructions", command=instructions)
data = [[1], [2], [3]] # this would be the data read from the file
b1 = data[0][0]
b2 = data[1][0]
b3 = data[2][0]
print "Read from file:", b1, b2, b3
setupView()
root.mainloop()

Looping in Tkinter isnt working as I intended

I am trying to update some buttons at the for loop in the end, which buttons I don't want to create individually.
I tried using:
While True:
root.update()
and the other type of update and also each one individually but no luck.
This is the code:
from tkinter import *
from tkinter.ttk import *
from PIL import Image
from functools import partial
import random
#define variables
#main_window_width = 500
#main_window_height = 500
#def functions
buttons_list = []
def InitButtons(n, k):
if k == 0:
k = n
for i in range(n):
InitButtons_column = i
if i//k > 0:
InitButtons_column = i%k
buttons_list.append(Label(main_window, image=button_img))
buttons_list[i].grid(row=(i//k), column=InitButtons_column)
def Hovering(e):
button_img = PhotoImage(file = '/home/klet/Desktop/projects/Python/GUI/button2.png')
buttons_list_button.config(image = button_img)
buttons_list_button.image = button_img
def Clicking(e):
button_img = PhotoImage(file = '/home/klet/Desktop/projects/Python/GUI/button3.png')
buttons_list_button.config(image = button_img)
buttons_list_button.image = button_img
def NotHovering(e):
button_img = PhotoImage(file = '/home/klet/Desktop/projects/Python/GUI/button1.png')
buttons_list_button.config(image = button_img)
buttons_list_button.image = button_img
def UpdatingButtons(n):
n.bind("<Enter>", Hovering)
n.bind("<Leave>", NotHovering)
n.bind("<Button-1>", Clicking)
n.bind("<ButtonRelease-1>", Hovering)
root = Tk()
button_img = PhotoImage(file = "/home/klet/Desktop/projects/Python/GUI/button1.png")
main_window = Frame(root)
main_window.place(relx=0.1, rely=0.1, relwidth=0.8, relheight=0.8)
InitButtons(10, 5)
#buttons_list_tmp1 = 0
#buttons_list_tmp1 += 1
#buttons_list_button =
#UpdatingButtons(buttons_list_tmp1)
#print(buttons_list_tmp1)
for i in range(len(buttons_list)):
buttons_list_button = buttons_list[i]
UpdatingButtons(buttons_list_button)
print(i)
root.mainloop()
The for loop above is only repeating once and i want it to be constantly updating, I also tried putting the whole code (within root and root.mainloop()) in a while loop but didnt work. I also searched some questions asked but they didnt seem to help.
I want to update the buttons(labels in this case) when I enter the button and when i click. Heres an example of what currently happening:
Link to vid
I made this account just to ask this question because I really dont like asking questions.

Can't run Toplevel window

I am new to python and Tkinter and I need some help. I try to write a program which will show toplevel window with message on defined time. I introduce date, hour and text to program. Press "START" button and wait until toplevel window with message appear.
Program work when I do not use thread, but main window "freeze" until loop is done. Then new toplevel window appear with text.
What I would like to do is to get rid of "freezing" main window. My idea was to use thread for loop executing. But it does not work. When loop is finished in a thread it should call function which cause to Toplevel window appear. But it does not. Moreover program freeze.
I know that I should not use thread within tkinter mainloop but I can not figure out how in other way I can get rid of "freezing" main window.
thank you for all your answers.
Rafal
here is my program:
from Tkinter import *
import time
import calendar
import datetime
import thread
class Okienka(object):
def __init__(self, master):
self.rok = Label(master, text = "Podaj rok: ")
self.rok.grid(row = 0, sticky = E)
self.miesiac = Label(master, text = "Podaj miesiac w formacie XX: ")
self.miesiac.grid(row = 1, sticky = E)
self.dzien = Label(master, text = "Podaj dzien w formacie XX: ")
self.dzien.grid(row = 2, sticky = E)
self.godzina = Label(master, text = "Podaj godzine w formacie XX:XX: ")
self.godzina.grid(row = 3, sticky = E)
self.przyp = Label(master, text = "Tekst przypomnienia: ")
self.przyp.grid(columnspan = 2)
self.erok = Entry(master, width = 4)
self.erok.grid(row = 0 ,column = 1)
self.emiesiac = Entry(master, width = 2)
self.emiesiac.grid(row = 1 ,column = 1)
self.edzien = Entry(master, width = 2)
self.edzien.grid(row = 2 ,column = 1)
self.egodzina = Entry(master, width = 5)
self.egodzina.grid(row = 3 ,column = 1)
self.eprzyp = Text(master, width = 50, heigh = 10, font = ("Helvetica",10))
self.eprzyp.grid(columnspan = 2)
self.button1 = Button(master, text = "START", fg = "red", command = watek)
self.button1.grid(columnspan = 2)
def watek():
thread.start_new_thread(Czas,())
def Czas():
data = "{0}-{1}-{2} {3}".format(c.erok.get(), c.emiesiac.get(), c.edzien.get(), c.egodzina.get())
while True:
aktualny_czas = datetime.datetime.today()
czas_str = time.strftime(str(aktualny_czas))
czas_str = czas_str[:16]
print czas_str
if data == czas_str:
okienko()
break
def okienko():
komunikat = c.eprzyp.get("1.0","end-1c")
top = Toplevel()
top.title("Przypomnienie")
msg = Message(top, text = komunikat)
msg.pack()
root = Tk()
c = Okienka(root)
root.mainloop()
Destroying the root window in a Tkinter application actually means destroying (freezing) the whole application, not only the root window. The root window is the main
window for the application; it is the first to pop up and must be the last to
go. If I understand correctly, your application does not have an actual main
window: if a second window is opened from the initial window, closing the
initial window should not quit the application. Am I right?
If I am, the way to do that with Tkinter (or tcl/tk) is to create a fake root
window and hide it. This window will only be used to quit the application when
the last window is closed:

Categories

Resources