Related
Hello i try to write a desktop app with tkinter for typing speed test i search but i cant find my answer is the any way to check the value of user entry in Textbox while app is running ? i need to compare user entry with test text i try to put the function before main loop when i init class but its just check the entry one time and also i try to use window.after()
this is my full code and problem is running last function
def run_test():
random_number = random.randint(1, 5)
test = session.query(data).filter(database.Database.id == random_number).first()
return test
class UserInterFace:
def __init__(self):
customtkinter.set_appearance_mode("dark")
customtkinter.set_default_color_theme("green")
self.window = CTk()
self.window.title("Typing Speed Test Application")
self.window.resizable(False, False)
self.window.config(padx=50, pady=50)
self.window.geometry("1000x700")
self.test = run_test()
self.wrong_entry = 0
self.correct_entry = 0
self.test_running = True
self.start_count = 5
self.start_test_btn = None
self.timer = None
self.start_test_time = None
self.end_test_time = None
self.user_input = []
self.get_ready_label = CTkLabel(master=self.window,
font=timer_font,
text="Get ready Test will be start in :",
text_color="#609EA2")
self.count_down_label = CTkLabel(master=self.window,
font=count_down_font,
text=str(self.start_count),
text_color="#820000")
self.label_test = CTkLabel(master=self.window,
font=my_font2,
text="",
wraplength=900,
)
self.best_score_record = CTkLabel(master=self.window,
font=my_font1,
text_color="#609EA2",
text="")
self.type_area = CTkTextbox(master=self.window,
width=900,
height=200)
self.main()
self.tset_user_text(self.test.text, self.user_input)
self.window.mainloop()
def start_test(self, test):
self.start_test_time = time.time()
self.get_ready_label.grid_forget()
self.count_down_label.grid_forget()
test_id = test.id
test_text = test.text
test_record = test.record
self.label_test.configure(text=test_text)
if test_record == 0:
test_record = "No Record Yet"
else:
test_record = f"Best Record : {test_record}"
self.best_score_record.configure(text=test_record)
self.best_score_record.grid(row=0, column=0)
self.label_test.grid(row=1, column=0, pady=(20, 0))
self.type_area.grid(row=2, column=0, pady=(20, 0))
self.type_area.focus_set()
self.user_input = self.type_area.get("1.0", END)
def main(self):
self.start_test_btn = CTkButton(master=self.window,
text="Start Test",
command=lambda: self.start_time(),
height=100,
width=300,
font=my_font1)
self.start_test_btn.grid(row=0, column=0, padx=(300, 0), pady=(250, 0))
def start_time(self):
if self.start_count >= 0:
self.start_test_btn.grid_forget()
self.get_ready_label.grid(row=0, column=0, pady=(50, 100), padx=(200, 0))
self.count_down_label.grid(row=1, column=0, padx=(200, 0))
self.count_down_label.configure(text=self.start_count)
self.start_count -= 1
self.timer = self.window.after(1000, self.start_time)
else:
self.window.after_cancel(self.timer)
self.start_test(self.test)
def tset_user_text(self, test_text, user_text):
print("test")
test_text_list = list(test_text)
user_text_list = list(user_text)
if len(test_text_list) <= len(user_text_list):
self.test_running = False
self.end_test_time = time.time()
for n in range(len(test_text_list)):
if test_text_list[n] == user_text_list[n]:
self.correct_entry += 1
else:
self.wrong_entry += 1
Okay, so take this with a grain of salt because I can't test this on CustomTkinter, but you should be able to bind an event to your textbox like so:
# call a method called 'on_type' whenever a key is released in the text area
self.type_area.bind('<KeyRelease>', self.on_type)
You can define a callback method to handle this event. Thanks to the binding above, the event parameter is passed to your callback automatically when the event is triggered
def on_type(self, event):
print(event) # do whatever you need to do
FYI: binding to '<KeyRelease>' avoids (some) issues with key repeat caused by held keys
I want to create a non blocking Gui with tkinter. The way I have seen it so far, you can do as with a mutliprocess. But now I have the problem that I want to access the mainloop of the gui again with the newly created thread and I always get an error here. can you jump back and forth between two threads or is there another method to not block the Gui?
import asyncio
import tkinter as tk
import multiprocessing as mp
class pseudo_example():
def app(self):
self.root = tk.Tk()
self.root.minsize(100,100)
start_button = tk.Button(self.root, text="start", command=lambda: mp.Process(target=self.create_await_fun).start())
start_button.pack() #
self.testfield = tk.Label(self.root, text="test")
self.testfield.pack()
#self.root.update_idletasks()
self.root.mainloop()
def create_await_fun(self):
asyncio.run(self.await_fun())
async def await_fun(self):
self.root.update_idletasks()
self.testfield["text"] = "start waiting"
await asyncio.sleep(2)
self.testfield["text"] = "end waiting"
if __name__ == '__main__':
try:
gui = pseudo_example()
gui.app()
except KeyboardInterrupt:
print("Interrupted")
sys.exit()
Error message:
[xcb] Unknown sequence number while processing queue
[xcb] Most likely this is a multi-threaded client and XInitThreads has not been called
[xcb] Aborting, sorry about that.
XIO: fatal IO error 0 (Success) on X server ":0"
after 401 requests (401 known processed) with 0 events remaining.
python3.8: ../../src/xcb_io.c:259: poll_for_event: Assertion `!xcb_xlib_threads_sequence_lost' failed.
i know that the after() method exists but i don't know how to use it with asyncio without starting the asyncio task. Asyncio is unnecessary in the minimal example but I need it for another application.
With respect to https://stackoverflow.com/a/47920128/15959848 I solved my problem. I have created an additional thread so that the GUI and the function each have a thread in which they can be executed.
EDIT Implementation of the shortened version of #Александр
class pseudo_example():
def __init__(self):
self.root = tk.Tk()
self.root.minsize(100, 100)
def app(self,):
self.start_button = tk.Button(self.root, text="start", command=lambda: self.create_await_funct())
self.start_button.pack()
self.testfield = tk.Label(self.root, text="output")
self.testfield.pack()
self.root.mainloop()
def create_await_funct(self):
threading.Thread(target=lambda loop: loop.run_until_complete(self.await_funct()),
args=(asyncio.new_event_loop(),)).start()
self.start_button["relief"] = "sunken"
self.start_button["state"] = "disabled"
async def await_funct(self):
self.testfield["text"] = "start waiting"
self.root.update_idletasks()
await asyncio.sleep(2)
self.testfield["text"] = "end waiting"
self.root.update_idletasks()
await asyncio.sleep(1)
self.testfield["text"] = "output"
self.root.update_idletasks()
self.start_button["relief"] = "raised"
self.start_button["state"] = "normal"
if __name__ == '__main__':
pseudo_example().app()
Tkinter doesn't support multi-task/multithreading.
You could use mtTkinter which provides thread safety when using multi-tasking: https://pypi.org/project/mttkinter/
You could also use a queue system to transfur data between two functions however you can't do anything with tkinter objects that cross between the two threads.
Using Queue with tkinter (and threading)
Idk whether this helps. Probably better than using asyncio.
ItzTheDodo.
An example with async-tkinter-loop library (written by me):
import asyncio
import tkinter as tk
import sys
from async_tkinter_loop import async_handler, async_mainloop
class pseudo_example():
def app(self):
self.root = tk.Tk()
self.root.minsize(100,100)
start_button = tk.Button(self.root, text="start", command=async_handler(self.await_fun))
start_button.pack()
self.testfield = tk.Label(self.root, text="test")
self.testfield.pack()
async_mainloop(self.root)
async def await_fun(self):
self.testfield["text"] = "start waiting"
await asyncio.sleep(2)
self.testfield["text"] = "end waiting"
if __name__ == '__main__':
gui = pseudo_example()
gui.app()
I usually use queues, I place you a sample.In the mask you will
see on the status bar change the time, which is managed by a separate thread
and by consulting the thread tail, see the class Clock, while you can use the various buttons without the mask freezing.I think you can take inspiration from this example.
#!/usr/bin/python3
import sys
import threading
import queue
import datetime
import time
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
class Clock(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.queue = queue.Queue()
self.check = True
def stop(self):
self.check = False
def run(self):
"""Feeds the tail."""
while self.check:
s = "Astral date: "
t = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
msg = "{0} {1}".format(s, t)
time.sleep(1)
self.queue.put(msg)
def check_queue(self, obj):
"""Returns a formatted string representing time.
obj in this case is the statusbar text"""
while self.queue.qsize():
try:
x = self.queue.get(0)
msg = "{0}".format(x)
obj.set(msg)
except queue.Empty:
pass
class Main(ttk.Frame):
def __init__(self, parent, ):
super().__init__(name="main")
self.parent = parent
self.text = tk.StringVar()
self.spins = tk.IntVar()
self.option = tk.IntVar()
self.check = tk.BooleanVar()
self.values = ('Apple','Banana','Orange')
self.status_bar_text = tk.StringVar()
self.init_status_bar()
self.init_ui()
def init_status_bar(self):
self.status = tk.Label(self,
textvariable=self.status_bar_text,
bd=1,
relief=tk.SUNKEN,
anchor=tk.W)
self.status.pack(side=tk.BOTTOM, fill=tk.X)
def init_ui(self):
f0 = ttk.Frame(self)
f1 = ttk.Frame(f0,)
ttk.Label(f1, text = "Combobox").pack()
self.cbCombo = ttk.Combobox(f1,state='readonly',values=self.values)
self.cbCombo.pack()
ttk.Label(f1, text = "Entry").pack()
self.txTest = ttk.Entry(f1, textvariable=self.text).pack()
ttk.Label(f1, text = "Spinbox").pack()
tk.Spinbox(f1, from_=0, to=15, textvariable= self.spins).pack()
ttk.Label(f1, text="Checkbutton:").pack()
ttk.Checkbutton(f1,
onvalue=1,
offvalue=0,
variable=self.check).pack()
ttk.Label(f1, text="Radiobutton:").pack()
for index, text in enumerate(self.values):
ttk.Radiobutton(f1,
text=text,
variable=self.option,
value=index,).pack()
ttk.Label(f1, text="Listbox:").pack()
self.ListBox = tk.Listbox(f1)
self.ListBox.pack()
self.ListBox.bind("<<ListboxSelect>>", self.on_listbox_select)
self.ListBox.bind("<Double-Button-1>", self.on_listbox_double_button)
f2 = ttk.Frame(f0,)
bts = [("Callback", 7, self.on_callback, "<Alt-k>"),
("Args", 0, self.on_args, "<Alt-a>"),
("kwargs", 1, self.on_kwargs, "<Alt-w>"),
("Set", 0, self.on_set, "<Alt-s>"),
("Reset", 0, self.on_reset, "<Alt-r>"),
("Close", 0, self.on_close, "<Alt-c>")]
for btn in bts:
ttk.Button(f2,
text=btn[0],
underline=btn[1],
command = btn[2]).pack(fill=tk.X, padx=5, pady=5)
self.parent.bind(btn[3], btn[2])
f1.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
f2.pack(side=tk.RIGHT, fill=tk.Y, expand=0)
f0.pack(fill=tk.BOTH, expand=1)
def on_open(self):
self.periodic_call()
def on_callback(self, evt=None):
print ("self.cbCombo = {}".format(self.cbCombo.get()))
print ("self.text = {}".format(self.text.get()))
print ("self.spins = {}".format(self.spins.get()))
print ("self.check = {}".format(self.check.get()))
print ("self.option = {}".format(self.option.get()))
if self.ListBox.curselection():
print("ListBox.curselection = {}".format(self.ListBox.curselection()[0]))
else:
print("{0}".format("No selected item on listbox"))
def on_args(self, evt=None):
print("args type: {}".format(type(self.master.args)))
for p, i in enumerate(self.master.args):
print(p, i)
def on_kwargs(self, evt=None):
print("kwargs type: {}".format(type(self.master.kwargs)))
for k, v in self.master.kwargs.items():
print("{0}:{1}".format(k,v))
def on_reset(self, evt=None):
self.text.set('')
self.spins.set(0)
self.check.set(0)
def on_set(self, evt=None):
self.cbCombo.current(1)
self.text.set('qwerty')
self.spins.set(42)
self.check.set(1)
self.option.set(1)
self.ListBox.delete(0, tk.END)
for i in self.values:
s = "{0}".format(i,)
self.ListBox.insert(tk.END, s)
self.ListBox.selection_set(1)
def on_listbox_select(self, evt=None):
if self.ListBox.curselection():
index = self.ListBox.curselection()
s = self.ListBox.get(index[0])
print("on_listbox_select: index = {0} values = {1}".format(index, s))
def on_listbox_double_button(self, evt=None):
if self.ListBox.curselection():
index = self.ListBox.curselection()
s = self.ListBox.get(index[0])
print("on_listbox_double_button: index = {0} values = {1}".format(index, s))
def periodic_call(self):
"""This funciont check the data returned from the clock class queue."""
self.parent.clock.check_queue(self.status_bar_text)
if self.parent.clock.is_alive():
self.after(1, self.periodic_call)
else:
pass
def on_close(self, evt=None):
self.parent.on_exit()
class App(tk.Tk):
"""Main Application start here"""
def __init__(self, *args, **kwargs):
super().__init__()
self.args = args
self.kwargs = kwargs
self.protocol("WM_DELETE_WINDOW", self.on_exit)
self.set_style(kwargs["style"])
self.set_title(kwargs["title"])
self.resizable(width=False, height=False)
#start clock on a separate thread...
self.set_clock()
w = Main(self)
w.on_open()
w.pack(fill=tk.BOTH, expand=1)
def set_clock(self,):
self.clock = self.get_clock()
self.clock.start()
def get_clock(self,):
"""Instance the clock."""
return Clock()
def set_style(self, which):
self.style = ttk.Style()
self.style.theme_use(which)
def set_title(self, title):
s = "{0}".format(title)
self.title(s)
def on_exit(self):
"""Close all"""
msg = "Do you want to quit?"
if messagebox.askokcancel(self.title(), msg, parent=self):
#stop the thread
if self.clock is not None:
self.clock.stop()
self.destroy()
def main():
args = []
for i in sys.argv:
args.append(i)
#('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
kwargs = {"style":"clam", "title":"Simple App",}
app = App(*args, **kwargs)
app.mainloop()
if __name__ == '__main__':
main()
I have a GUI for the purpose of recording audio. There are two buttons, start and stop recording.
There is a loop inside the function for start recording which i cannot remove. When the start recording button is pressed, the stop button doesn't respond (because of the loop in start which I cannot remove for a few reasons). I would like to know if there is a way to solve this issue and get both buttons to respond even when the program is in the loop of start recording which is an infinite loop.I'm using python2. The code looks something like the following,
class RecAUD:
def __init__(self, chunk=4000, frmat=pyaudio.paInt16, channels=1, rate=44100, py=pyaudio.PyAudio()):
# Start Tkinter and set Title
self.main = tk.Tk()
self.collections = []
self.var = StringVar()
self.var.set('READY')
self.main.geometry('1200x500')
self.main.title('Demo')
self.CHUNK = chunk
self.FORMAT = frmat
self.CHANNELS = channels
self.RATE = rate
self.p = py
self.st = 1
print("----------------------record device list---------------------")
info = self.p.get_host_api_info_by_index(0)
numdevices = info.get('deviceCount')
for i in range(0, numdevices):
if (self.p.get_device_info_by_host_api_device_index(0, i).get('maxInputChannels')) > 0:
print("Input Device id ", i, " - ", self.p.get_device_info_by_host_api_device_index(0, i).get('name'))
print("-------------------------------------------------------------")
self.index = int(input())
print("recording via index "+str(self.index))
self.stream = self.p.open(format=self.FORMAT, channels=self.CHANNELS, rate=self.RATE, input=True,input_device_index = self.index, frames_per_buffer=self.CHUNK)
self.buttons = tkinter.Frame(self.main, padx=1, pady=50)
self.buttons.pack(fill=tk.BOTH)
photo = PhotoImage(file = r"stt.png")
photoimage = photo.subsample(5, 5)
self.strt_rec = tkinter.Button(self.buttons, width=100, padx=8, pady=25, text='\n\n\n\nStart Recording', command=lambda: self.start_record(), bg='white', image = photoimage, compound = CENTER)
self.strt_rec.grid(row=0, column=0, columnspan=2, padx=50, pady=5)
self.stop_rec = tkinter.Button(self.buttons, width=100, padx=8, pady=25, text='\n\n\n\nStop Recording', command=lambda: self.stop_record(), bg='white', image = photoimage, compound = CENTER)
self.stop_rec.grid(row=0, column=1, columnspan=2, padx=450, pady=5)
self.op_text = Label(self.main,textvariable = self.var,foreground="green",font = "Times 30 bold italic")
self.op_text.place(x=350,y=100,anchor=NW)
self.op_text.pack()
tkinter.mainloop()
def start_record(self):
WAVE_OUTPUT_FILENAME = "recordedFile.wav"
while True:
data_frame = self.stream.read(self.CHUNK)
data_int16_frame = list(struct.unpack(str(self.CHUNK) + 'h', data_frame))
...
...
def stop_record(self):
self.stream.stop_stream()
self.stream.close()
self.p.terminate()
guiAUD = RecAUD()
How do I go about solving this issue, what should I add to the above code to make the buttons responsive at anytime? Is multithreading required? If yes, how can I introduce it for the buttons in the above code? Any suggestions are much appreciated! Thanks in advance!
You can use threading a module used to make threads
import threading
class cls():
def __init__():
self.thrun = True
def start_record(self):
WAVE_OUTPUT_FILENAME = "recordedFile.wav"
while self.thrun:
data_frame = self.stream.read(self.CHUNK)
data_int16_frame = list(struct.unpack(str(self.CHUNK) + 'h', data_frame))
def click(self):
self.th = threading.Thread(target=start_record)
def stop(self):
self.thrun = False
self.th.join()
For example, you can add a update in a while True to kke the GUI "alive".
In the code below, press first OK, it enters in a while True loop but with update, GUI reacts when you press KO:
import tkinter as tk
a = 0
def action():
global a
while True:
if a == 0:
b.configure(bg="red")
else:
b.configure(bg="blue")
w.update()
def reaction():
global a
a = 1
w = tk.Tk()
b = tk.Button(w, text="OK", command=action)
c = tk.Button(w, text="K0", command=reaction)
b.pack()
c.pack()
w.mainloop()
The danger is to enter in multiple calls to action and worse in recursive calls... you, in these cases, should manage this in your start callback.
I I have 2 functions and the second function should run after the button in the first one has been clicked. This works fine, however I need the number that has been entered to go into a variable and so far the .get() function is not working and im not sure what to do.
I have looked at a lot of different examples, including login and sign up gui's however none of them have been able to help.
from tkinter import *
import tkinter.messagebox as box
def enter_rle_1():
enter_rle = Tk() #do not remove
enter_rle.title('Enter RLE') #do not remove
frame = Frame(enter_rle) #do not remove
label_linecount = Label(enter_rle,text = 'Linecount:')
label_linecount.pack(padx=15,pady= 5)
linecount = Entry(enter_rle,bd =5)
linecount.pack(padx=15, pady=5)
ok_button = Button(enter_rle, text="Next", command = linecount_button_clicked)
ok_button.pack(side = RIGHT , padx =5)
frame.pack(padx=100,pady = 19)
enter_rle.mainloop()
def linecount_button_clicked():
Linecount = linecount.get()
if int(Linecount) < 3:
tm.showinfo("Error", "Enter a number over 3")
elif int(Linecount) > 1000000000:
tm.showinfo("Error", "Enter a number under 1,000,000,000")
else:
print("fdsfd")
enter_rle_1()
I expect there to be a popup telling me the number is too big or too small, depending on wether the number is or not, and if it is a good number, i just have it set as a print as some code to test to see if it works before i move on.
Define a String variable for the Entry widget (Make sure it is global defined):
global linecount_STR
linecount_STR = StringVar()
linecount_STR.set('Enter value here')
linecount = Entry(enter_rle,bd =5,textvariable=linecount_STR)
linecount.pack(padx=15, pady=5)
The number entered there can then be read with int(linecount_STR.get()).
i suggest an OO approach, look this code
#!/usr/bin/python3
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
class Main(ttk.Frame):
def __init__(self, parent):
super().__init__()
self.parent = parent
self.vcmd = (self.register(self.validate), '%d', '%P', '%S')
self.linecount = tk.IntVar()
self.init_ui()
def init_ui(self):
self.pack(fill=tk.BOTH, expand=1)
f = ttk.Frame()
ttk.Label(f, text = "Linecount").pack()
self.txTest = ttk.Entry(f,
validate='key',
validatecommand=self.vcmd,
textvariable=self.linecount).pack()
w = ttk.Frame()
ttk.Button(w, text="Next", command=self.on_callback).pack()
ttk.Button(w, text="Close", command=self.on_close).pack()
f.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)
def on_callback(self,):
#print ("self.text = {}".format(self.linecount.get()))
x = self.linecount.get()
if x < 3:
msg = "Enter a number over 3"
elif x > 1000000000:
msg = "Enter a number under 1,000,000,000"
else:
msg = "You ahve enter {0}".format(x)
messagebox.showinfo(self.parent.title(), msg, parent=self)
def on_close(self):
self.parent.on_exit()
def validate(self, action, value, text,):
# action=1 -> insert
if action == '1':
if text in '0123456789':
try:
int(value)
return True
except ValueError:
return False
else:
return False
else:
return True
class App(tk.Tk):
"""Start here"""
def __init__(self):
super().__init__()
self.protocol("WM_DELETE_WINDOW", self.on_exit)
self.set_title()
self.set_style()
frame = Main(self,)
frame.pack(fill=tk.BOTH, expand=1)
def set_style(self):
self.style = ttk.Style()
#('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
self.style.theme_use("clam")
def set_title(self):
s = "{0}".format('Enter RLE')
self.title(s)
def on_exit(self):
"""Close all"""
if messagebox.askokcancel(self.title(), "Do you want to quit?", parent=self):
self.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()
notice this line at the begin....
self.linecount = tk.IntVar()# declare an integer variable
self.vcmd = (self.register(self.validate), '%d', '%P', '%S')#register a function to allow only integer in your text widget.
I'm building a music player with Pygame & Tkinter and currently trying to add a working time slider that allows you to jump to a specific point in a song by dragging the slider.
I set the value of the end of the slider ('to' in config) to the length of the current song then update the slider with 'after' as shown below:
def update_timeslider(self, _ = None):
time = (pygame.mixer.music.get_pos()/1000)
timeslider.set(time)
self.after(1000, self.update_timeslider)
This works in moving the slider along with the time of the song but now I'm trying to implement the cue function and having issues. I try to update song position to the value of the slider with
def cue(self, _ = None):
pygame.mixer.music.set_pos(timeslider.get())
Now when I play the song, its very choppy. When I move the slider, it works for a split second before the 'after' function updates but then it jumps back to the position it was before being moved. I tried increasing the refresh rate but it just causes it to jump back faster and remains just as choppy.
Is there a better way to do this?
Full code:
import os
import pygame
import tkinter
from tkinter.filedialog import askdirectory
from tkinter import *
from tkinter import ttk
playlist = []
index = 0
paused = False
class Application(tkinter.Tk):
def __init__(self, parent):
tkinter.Tk.__init__(self, parent)
self.minsize(400,400)
self.parent = parent
self.main()
def main(self):
global v
global songlabel
global listbox
global volumeslider
global timeslider
global time_elapsed
global songlength
self.configure(background='grey')
self.grid()
self.listbox = Listbox(self, width=20, height=25, relief='ridge', bd=3)
self.listbox.grid(padx=30, pady=15, row=1, columnspan=11, sticky='NSEW')
v = StringVar()
songlabel = tkinter.Label(self, textvariable=v, width=30, anchor="n")
rewbtn = PhotoImage(file="rew.gif")
stopbtn = PhotoImage(file="stop.gif")
playbtn = PhotoImage(file="play.gif")
pausebtn = PhotoImage(file="pause.gif")
ffbtn = PhotoImage(file="ff.gif")
prevbutton = Button(self, width=30, height=30, image=rewbtn, anchor='w')
prevbutton.image = rewbtn
prevbutton.bind("<Button-1>", self.prevsong)
prevbutton.grid(row=10, column=0, padx=(30,0), sticky='w')
playbutton = Button(self, width=30, height=30, image=playbtn, anchor='w')
playbutton.image = playbtn
playbutton.bind("<Button-1>", self.play)
playbutton.grid(row=10, column=1, sticky='w')
pausebutton = Button(self, width=30, height=30, image=pausebtn, anchor='w')
pausebutton.image = pausebtn
pausebutton.bind("<Button-1>", self.pause)
pausebutton.grid(row=10, column=2, sticky='w')
stopbutton = Button(self, width=30, height=30, image=stopbtn, anchor='w')
stopbutton.image = stopbtn
stopbutton.bind("<Button-1>", self.stop)
stopbutton.grid(row=10, column=3, sticky='w')
nextbutton = Button(self, width=30, height=30, image=ffbtn, anchor='w')
nextbutton.image = ffbtn
nextbutton.bind("<Button-1>", self.nextsong)
nextbutton.grid(row=10, column=4, sticky='w')
volumeslider = Scale(self, from_=0, to = 1, resolution = 0.01, orient = HORIZONTAL, showvalue = 'yes', command = self.change_vol)
volumeslider.grid(row=10, column=8, columnspan=3, padx=30, pady=(0,10), sticky='wse')
volumeslider.set(50)
timeslider = Scale(self, from_=0, to=100, resolution=1, orient=HORIZONTAL, showvalue = 'no', command=self.cue)
timeslider.grid(row=12, column=0, columnspan=11, padx = 30, sticky='wse')
timeslider.set(0)
time_elapsed = Label(text="0:00:00")
time_elapsed.grid(row=13, columnspan=11, padx=(30,0), pady=(0,30), sticky='ws')
# time_remaining = Label(text="0:00:00")
# time_remaining.grid(row=13, column = 7, columnspan=5, padx=(0,30), pady=(0,30), sticky='se')
# FILE OPEN
self.directorychooser()
playlist.reverse()
for items in playlist:
self.listbox.insert(0, items)
playlist.reverse()
self.listbox.bind("<Double-Button-1>", self.selectsong)
self.listbox.bind("<Return>", self.selectsong)
songlabel.grid(row = 0, column = 0, columnspan = 10, padx = 55, pady=(10,0), sticky=W+N+E)
# GRID WEIGHT
self.grid_columnconfigure(5,weight=1)
self.grid_columnconfigure(7,weight=1)
self.grid_rowconfigure(1,weight=1)
def prevsong(self, event):
global index
if index > 0:
index-=1
print(index)
elif index == 0:
index = len(playlist)-1
pygame.mixer.music.load(playlist[index])
self.set_timescale()
pygame.mixer.music.play()
self.get_time_elapsed()
self.update_timeslider()
self.update_currentsong()
def play(self, event):
self.set_timescale()
pygame.mixer.music.play()
self.get_time_elapsed()
self.update_timeslider()
self.update_currentsong()
def pause(self, event):
global paused
if paused == True:
pygame.mixer.music.unpause()
paused = False
elif paused == False:
pygame.mixer.music.pause()
paused = True
def nextsong(self, event):
global index
if index < len(playlist)-1:
index+=1
elif index == (len(playlist)-1):
index = 0
pygame.mixer.music.load(playlist[index])
self.set_timescale()
pygame.mixer.music.play()
self.get_time_elapsed()
self.update_timeslider()
self.update_currentsong()
def stop(self, event):
pygame.mixer.music.stop()
v.set("")
return songlabel
def selectsong(self, event):
global index
global songtime
global songlength
idx = self.listbox.curselection()
index = idx[0]
pygame.mixer.music.load(playlist[index])
self.set_timescale()
pygame.mixer.music.play()
self.get_time_elapsed()
# self.get_time_remaining()
self.update_timeslider()
self.update_currentsong()
def change_vol(self, _ = None):
pygame.mixer.music.set_volume(volumeslider.get())
def cue(self, _ = None):
pygame.mixer.music.set_pos(timeslider.get())
def getsonglen(self):
s = pygame.mixer.Sound(playlist[index])
songlength = s.get_length()
return songlength
def set_timescale(self):
songlength = self.getsonglen()
timeslider.config(to=songlength)
def get_time_elapsed(self):
global time_elapsed
time = int(pygame.mixer.music.get_pos()/1000)
m, s = divmod(time, 60)
h, m = divmod(m, 60)
clock = "%d:%02d:%02d" % (h, m, s)
time_elapsed.configure(text=clock)
self.after(100, self.get_time_elapsed)
# def get_time_remaining(self):
# global time_remaining
# time = int(pygame.mixer.music.get_pos()/1000)
# songlen = int(self.getsonglen())
# rem = songlen - time
# m, s = divmod(rem, 60)
# h, m = divmod(m, 60)
# clock2 = "%d:%02d:%02d" % (h, m, s)
# time_remaining.configure(text=clock2)
# self.after(100, self.get_time_remaining)
def update_timeslider(self, _ = None):
time = (pygame.mixer.music.get_pos()/1000)
timeslider.set(time)
self.after(10, self.update_timeslider)
def update_currentsong(self):
global index
global songlabel
v.set(playlist[index])
return songlabel
def directorychooser(self):
directory = askdirectory()
os.chdir(directory)
for files in os.listdir(directory):
if files.endswith(".flac"):
realdir = os.path.realpath(files)
playlist.append(files)
print(files)
pygame.mixer.init()
pygame.mixer.music.load(playlist[0])
self.update_currentsong()
app = Application(None)
app.mainloop()
The problem appears to be that update_timeslider is being called way more times than you think it does.
When you do this:
self.update_timeslider()
... it causes self.update_timeslider to be called 100 times per second.
The problem is that you call that from multiple places in the code, which mean you may ultimately be calling that function 500-1000 times per second or even more. If each call takes a large fraction of a second, you'll end up spending more CPU time updating the slider than you are playing the sound.
When you call after it will return an id. You can use this id to later cancel any existing call before starting a new loop. For example, you might want to do something like this:
class Application(tkinter.Tk):
def __init__(self, parent):
...
self.after_id = None
def update_timeslider(self, _ = None):
if self.after_id is not None:
self.after_cancel(self.after_id)
self.after_id = None
time = (pygame.mixer.music.get_pos()/1000)
timeslider.set(time)
self.after_id = self.after(1000, self.update_timeslider)
Instead of using pygame.mixer.Sound() in your getsonglen() function, use Mutagen for getting the song length.
pygame.mixer.Sound() at first converts the song to a Sound, I exactly don't know what happens, but it probably causes it to use more CPU and that's why the audio gets choppy.
I also faced the same problem, so I used Mutagen for getting the song's length. It worked fine for me.
from mutagen.mp3 import MP3
song = 'your song path'
total_length = MP3(song).info.length
Hope it helps!