I have this timer program in python with a Tkinter GUI. I am able to run the timer user inputs, but I am having trouble pausing or stopping the timer from a tkinter button. I tried creating a variable that changes when the stop button press to initiate time.sleep() in my if statement, but no luck. Any ideas?
from tkinter import Tk, Label, StringVar
from tkinter.ttk import Button, Entry
class Timer:
def __init__(self, master):
self.seconds = 0
self.f = False
self.time = StringVar()
self.time_label = Label(master, relief='flat', font=("Cambria", 20),
textvariable=self.time)
self.hr = StringVar()
self.hr_label = Label(master, text='Hours:').grid(row=1, column=1, padx=5, pady=1)
self.entry_hr = Entry(master, textvariable=self.hr, width=4)
self.entry_hr.grid(row=1, column=2)
self.mins = StringVar()
self.min_label = Label(master, text='Minutes:').grid(row=2, column=1, padx=5, pady=1)
self.entry_min = Entry(master, textvariable=self.mins, width=4)
self.entry_min.grid(row=2, column=2)
self.secs = StringVar()
self.secs_label = Label(master, text='Seconds:').grid(row=3, column=1, padx=5, pady=1)
self.entry_sec = Entry(master, textvariable=self.secs, width=4)
self.entry_sec.grid(row=3, column=2)
self.time.set('00:00:00')
self.start_button = Button(master, text='Start')
self.start_button.bind('<Button-1>', self.start_countdown)
self.stop_button = Button(master, text='Stop')
self.time_label.grid(row=0, columnspan=4, padx=30, pady=10)
self.start_button.grid(row=4, columnspan=4, pady=(10,20))
self.stop_button.grid(row=5, columnspan=4, pady=(10,20))
def set_time(self, hours, minutes, seconds):
self.seconds = hours * 3600 + minutes * 60 + seconds
self.time.set(self.format_time(self.seconds))
def start_countdown(self, event):
f = False
h = self.entry_hr.get()
m = self.entry_min.get()
s = self.entry_sec.get()
h,m,s = map(lambda x: int(x) if x else 0, (h,m,s))
self.set_time(h,m,s)
self.countdown()
def stop(self):
global f
f= True
def countdown(self):
if self.seconds <= 0:
return
else:
self.end_label = Label( text ="Time's up")
if self.f == True:
time.sleep()
self.time.set(self.format_time(self.seconds))
self.seconds -= 1
self.time_label.after(1000, self.countdown)
def format_time(self, seconds):
h = seconds // 3600
m = (seconds - h*3600) // 60
s = seconds - h*3600 - m*60
return '{:0>2}:{:0>2}:{:0>2}'.format(h,m,s)
if __name__ == '__main__':
root = Tk()
timer = Timer(root)
root.mainloop()
First of all your Button widget self.stop_button doesn't have a command or binding associated with it, so it doesn't actually do anything.
Secondly, even if it did call the function you're changing the value of the global variable f but in your if statement you're referencing the local variable self.f (Timer.f).
Thirdly, you're not calling time.sleep() correctly, it requires a value in seconds to sleep for.
And fourthly, time.sleep() would only delay the function from running for the period of time you specify and would eventually cause the timer to start ticking down again.
Instead, you should do something like the below (after associating the Button widget to the function self.stop()):
def stop(self):
self.f = True
def countdown(self):
if self.f == False:
print(self.f)
if self.seconds <= 0:
return
else:
self.end_label = Label( text ="Time's up")
self.time.set(self.format_time(self.seconds))
self.seconds -= 1
self.time_label.after(1000, self.countdown)
This will mean that every time we cycle through the function the first thing we do is check whether self.f is False, if it isn't then the timer stops where it is.
Related
So Im reading a serial device that sends data every couple of seconds, I have my timer running on the screen and the goal is that every time it updates the serial read it restarts the timer to zero.. This allows me to visually see if Ive lost contact with the device. Ive spent alot of time searching to no avail. Not real good with python so any help would be appreciated.
from tkinter import *
from serial import Serial
import time
from serial.threaded import ReaderThread, Protocol
import threading
import tkinter as tk
#Input ED Number
ed_num = 0
def tkinter_input(prompt=""):
global ed_num
root = tk.Tk()
root.wm_attributes('-fullscreen',1)
root.configure(background='black')
tk.Label(root, font=('Ariel', 100), foreground='Green',
background='Black', text=prompt).pack()
entry = tk.Entry(root, font=('Ariel', 100),justify='center', foreground='Black',
background='white')
entry.focus()
entry.pack()
result = None
def callback(event):
nonlocal result
result = entry.get()
root.destroy()
entry.bind("<Return>", callback)
root.mainloop()
return result
result = tkinter_input("Scan Dosimeter")
ed_num = result
print(ed_num)
#Start Loop
root = Tk()
#Sets Main Window
root.wm_attributes('-fullscreen',1)
root.configure(background='black')
'''Shuts Down App will need to remove'''
button_quit = Button(root, text = 'QUIT', command = root.destroy).pack(anchor=NE)
#Container Frame
mainframe= Frame(root, bd=4, bg='Black')
mainframe.pack(anchor=CENTER, expand=True, fill=BOTH, padx=(20,20), pady=(20,20))
mainframe.grid_columnconfigure(0, weight=1)
mainframe.grid_rowconfigure(0, weight=1)
mainframe.grid_rowconfigure(1, weight=1)
mainframe.grid_rowconfigure(2, weight=1)
#ID Label
ed_id_label = Label(mainframe,
font=('Ariel', 50), foreground='Green',
background='Black', anchor='w')
ed_id_label.grid(row=0, column=0, sticky='ew', padx=(100,100), pady=(100,100))
#Dose Label
doselabel = Label(mainframe,
font=('Ariel', 130), foreground='Green',
background='Black', anchor='w')
doselabel.grid(row=1, column=0, sticky='ew', padx=(100,100), pady=(100,100))
# Rate Label
ratelabel = Label(mainframe,
font=('Ariel', 130), foreground='Green',
background='Black', anchor='w')
ratelabel.grid(row=2, column=0, sticky='ew', padx=(100,100), pady=(100,100))
#Timer Label
timelabel = Label(mainframe,
font=('Ariel', 20), foreground='Green',
background='Black', anchor='w')
timelabel.grid(row=3, column=0, sticky='se')
#Data Timer
seconds = 0
def countup(seconds):
seconds += 1
timelabel['text'] = str(seconds)+"s"
root.after(1000, countup, seconds)
#Serial Reader Thread
class SerialReaderProtocolRaw(Protocol):
def data_received(self, data):
"""Called with snippets received from the serial port"""
updateLabelData(data)
def updateLabelData(data):
data = data.decode("utf-8")
if data[0:6] == ed_num:
ed_id_label['text']='Ed id: ' +data[0:6]
doselabel['text']='Dose: ' + data[11:14]+'.'+data[14:15]
if data[21:22] == '0':
ratelabel['text']='Rate: ' + data[18:19]
if data[21:22] == '1':
ratelabel['text']='Rate: ' + data[18:20]
if data[21:22] == '2':
ratelabel['text']='Rate: ' + data[18:21]
if data[21:22] == '3':
ratelabel['text']='Rate: ' + data[18:22]
if data[6:7] =='1':
doselabel.config(bg= "red")
if data[6:7] =='2':
ratelabel.config(bg= "red")
root.update_idletasks()
# Initiate serial port
serial_port = Serial('COM3', baudrate = 57600)
# Initiate ReaderThread
reader = ReaderThread(serial_port, SerialReaderProtocolRaw)
# Initiate Counter Thread
countupthread = threading.Thread(target=countup, args = (seconds,))
# Start reader/counter
countupthread.start()
reader.start()
# Join Threads
countupthread.join()
root.after(1, countup, seconds)
root.mainloop()
There are at least two solutions I can think of off the top of my head: cancel and restart the counter, or make seconds global.
Stop and restart the counter
When you schedule something with after, it returns an identifier. You can pass this identifier to after_cancel to prevent that function from running. Since you're not using classes, you can use a global variable to track this identifier.
Example:
def countup(seconds):
global after_id
seconds += 1
timelabel['text'] = str(seconds)+"s"
after_id = root.after(1000, countup, seconds)
Then, whenever you need to reset the timer you can use the following two lines to cancel and restart countup:
root.after_cancel(after_id)
countup(0)
Make seconds global
Instead of passing seconds to the countup function, have it use a global variable. You can then just reset this variable whenever you want.
seconds = 0
def countup():
global seconds
seconds += 1
timelabel['text'] = str(seconds)+"s"
after_id = root.after(1000, countup, seconds)
With that, whenever you want to reset the counter you simply need to reset seconds:
global seconds
seconds = 0
I have a off delay program in which when I select the input checkbutton, the output is 1. When I deselect the input checkbutton, the output goes back to 0 after a timer (set up in a scale). For that I use the after method. That part works. My problem is that I want to reset the timer if the checkbutton is selected again before the output went to 0; but once the checkbutton is selected the first time, the after method get triggered and it doesn't stop. I'm trying to use after_cancel, but I can't get it to work. Any solution?
from tkinter import *
root = Tk()
t1= IntVar()
out = Label(root, text="0")
remain_time = IntVar()
grab_time = 1000
def start_timer(set_time):
global grab_time
grab_time = int(set_time) * 1000
def play():
if t1.get() == 1:
button1.configure(bg='red')
out.configure(bg="red", text="1")
else:
button1.configure(bg='green')
def result():
out.configure(bg="green", text="0")
out.after(grab_time,result)
button1 = Checkbutton(root,variable=t1, textvariable=t1, command=play)
time = Scale(root, from_=1, to=10, command=start_timer)
button1.pack()
time.pack()
out.pack()
root.mainloop()
Expected: when press the checkbutton before the output went to 0, reset the counter.
So you could use the .after_cencel when the value of checkbutton is 1:
from tkinter import *
root = Tk()
t1= IntVar()
out = Label(root, text="0")
remain_time = IntVar()
grab_time = 1000
def start_timer(set_time):
global grab_time
grab_time = int(set_time) * 1000
def play():
if t1.get() == 1:
button1.configure(bg='red')
out.configure(bg="red", text="1")
try: # when the first time you start the counter, root.counter didn't exist, use a try..except to catch it.
root.after_cancel(root.counter)
except :
pass
else:
button1.configure(bg='green')
def result():
out.configure(bg="green", text="0")
root.counter = out.after(grab_time,result)
button1 = Checkbutton(root,variable=t1, textvariable=t1, command=play)
time = Scale(root, from_=1, to=10, command=start_timer)
button1.pack()
time.pack()
out.pack()
root.mainloop()
I have the following code for stop watch. when you run it, you can see an stop watch with start button.
I have another code for calculation of long equations which takes a long time to solve. How can I put that code inside of the following code: when I run the code the stop watch start counting automatically and when the code finished its calculation, stopwatch stop counting.
import tkinter
import time
class StopWatch(tkinter.Frame):
#classmethod
def main(cls):
tkinter.NoDefaultRoot()
root = tkinter.Tk()
root.title('Stop Watch')
root.resizable(True, False)
root.grid_columnconfigure(0, weight=1)
padding = dict(padx=5, pady=5)
widget = StopWatch(root, **padding)
widget.grid(sticky=tkinter.NSEW, **padding)
root.mainloop()
def __init__(self, master=None, cnf={}, **kw):
padding = dict(padx=kw.pop('padx', 5), pady=kw.pop('pady', 5))
super().__init__(master, cnf, **kw)
self.grid_columnconfigure(1, weight=1)
self.grid_rowconfigure(1, weight=1)
self.__total = 0
self.__label = tkinter.Label(self, text='Total Time:')
self.__time = tkinter.StringVar(self, '0.000000')
self.__display = tkinter.Label(self, textvariable=self.__time)
self.__button = tkinter.Button(self, text='Start', command=self.__click)
self.__label.grid(row=0, column=0, sticky=tkinter.E, **padding)
self.__display.grid(row=0, column=1, sticky=tkinter.EW, **padding)
self.__button.grid(row=1, column=0, columnspan=2,
sticky=tkinter.NSEW, **padding)
def __click(self):
if self.__button['text'] == 'Start':
self.__button['text'] = 'Stop'
self.__start = time.clock()
self.__counter = self.after_idle(self.__update)
else:
self.__button['text'] = 'Start'
self.after_cancel(self.__counter)
def __update(self):
now = time.clock()
diff = now - self.__start
self.__start = now
self.__total += diff
self.__time.set('{:.6f}'.format(self.__total))
self.__counter = self.after_idle(self.__update)
if __name__ == '__main__':
StopWatch.main()
thank you so much
I am working with tkinter library in python. I have a main window which has several buttons and when clicked those buttons a new window will popup which also has a button called cancel. I want to make that cancel button to all the windows.
I tried the following solution, which only closes the current window.
from tkinter import *
from tkinter import ttk
import tkinter.messagebox
import datetime
import tkinter as tk
class AlertDialog:
def __init__(self):
self.invalidDiag = tk.Toplevel()
invalidInput = tk.Label(master=self.invalidDiag,
text='Error: Invalid Input').grid(row=1, column=1)
closeButton = tk.Button(master=self.invalidDiag,
text='Close',
command=self.invalidDiag.destroy).grid(row=2, column=1)
def start(self):
# self.invalidDiag.grab_set() #takes control over the dialog (makes it active)
self.invalidDiag.wait_window()
class QuitDialog():
def __init__(self, ):
self.quitDialog = tk.Toplevel()
warnMessage = tk.Label(master=self.quitDialog,
text='Are you sure that you want to quit? ').grid(row=1, column=1)
cancelButton = tk.Button(master= self.quitDialog ,
text='Cancel',
command = self.quitALL).grid(row=2, column=1)
def start(self):
# self.invalidDiag.grab_set() #takes control over the dialog (makes it active)
self.quitDialog.wait_window()
def quitALL(self):
self.quitDialog.destroy()
tc =TimeConverter()
tc.destroyit()
class TimeConverter:
def __init__(self):
self.mainWindow = tk.Tk()
self.mainWindow.title("Seconds Converter")
self.results = tk.StringVar()
self.inputSecs = tk.StringVar()
secLabel = tk.Label(master=self.mainWindow,
text="Seconds:").grid(row=0, sticky="W")
resultLabel = tk.Label(master=self.mainWindow,
text="Converted Time:\n(H:M:S)").grid(row=1, sticky="W")
calcResults = tk.Label(master=self.mainWindow,
background='light gray', width=20,
textvariable=self.results,
anchor="w").grid(row=1, column=1)
secEntry = tk.Entry(master=self.mainWindow,
width=24,
textvariable=self.inputSecs).grid(row=0, column=1)
calcButton = tk.Button(master=self.mainWindow,
text='Calculate',
command=self.SecondsToHours).grid(row=2,
column=0, sticky="w")
# quitButton = tk.Button(master=self.mainWindow,
# text='Quit',
# command=self.mainWindow.destroy).grid(row=2, column=1, sticky="E")
quitButton = tk.Button(master=self.mainWindow,
text='Quit',
command=self.showQuitDialog).grid(row=3, column=1, sticky="E")
def invalidInputEntered(self):
errorDiag = AlertDialog()
errorDiag.start()
def showQuitDialog(self):
quitdialog = QuitDialog()
quitdialog.start()
def startDisplay(self) -> None:
self.mainWindow.mainloop()
def destroyit(self):
self.mainWindow.destroy()
def SecondsToHours(self):
try:
inputseconds = int(self.inputSecs.get())
seconds = int(inputseconds % 60)
minutes = int(((inputseconds - seconds) / 60) % 60)
hours = int((((inputseconds - seconds) / 60) - minutes) / 60)
tempResults = str(hours) + ':' + str(minutes) + ':' + str(seconds)
self.results.set(tempResults)
return
except ValueError:
self.invalidInputEntered()
#self.showQuitDialog()
if __name__ == '__main__':
TimeConverter().startDisplay()
You are importing tkinter 2 times here. Onces with * and ones as tk.
Just use:
import tkinter as tk
this will help you avoid overriding anything that other libraries imports or having tkinter's functions overwritten by other imports.
You have a very unorthodox way of creating your tkinter app but if you wish to keep everything as is here is what you need to change:
Lets remove the cancel button from your quitDialog window and then add a yes and no button. This will server to allow you to either say yes to destroy all windows or to say no to only destroy the quitDialog window.
First we need to add an arguement to your QuitDialog class so we can pass the any window or frame we want to it.
So add the instance argument to your QuitDialog() class like below:
class QuitDialog():
def __init__(self, instance):
self.instance = instance
Now replace:
cancelButton = tk.Button(master= self.quitDialog ,
text='Cancel',
command = self.quitALL).grid(row=2, column=1)
With:
quitButton = tk.Button(master= self.quitDialog ,
text='Yes', command = self.quitALL).grid(row=2, column=1)
cancelButton = tk.Button(master= self.quitDialog,
text='No', command = lambda: self.quitDialog.destroy()).grid(row=2, column=2)
Then lets change your quitALL() method to:
def quitALL(self):
self.quitDialog.destroy()
self.instance.destroy()
This will use our instance argument to destroy a window we pass in.
Now change your showQuitDialog() method to:
def showQuitDialog(self):
quitdialog = QuitDialog(self.mainWindow)
quitdialog.start()
As you can see we are now passing the the tk window self.mainWindow to the QuitDialog class so we can decide on weather or not to close it.
Below is the copy past version of your code that should work as you need it to:
import tkinter as tk
from tkinter import ttk
import tkinter.messagebox
import datetime
class AlertDialog:
def __init__(self):
self.invalidDiag = tk.Toplevel()
invalidInput = tk.Label(master=self.invalidDiag,
text='Error: Invalid Input').grid(row=1, column=1)
closeButton = tk.Button(master=self.invalidDiag,
text='Close',
command=self.invalidDiag.destroy).grid(row=2, column=1)
def start(self):
# self.invalidDiag.grab_set() #takes control over the dialog (makes it active)
self.invalidDiag.wait_window()
class QuitDialog():
def __init__(self, instance):
self.instance = instance
self.quitDialog = tk.Toplevel()
warnMessage = tk.Label(master=self.quitDialog,
text='Are you sure that you want to quit? ').grid(row=1, column=1, columnspan=2)
quitButton = tk.Button(master= self.quitDialog ,
text='Yes',
command = self.quitALL).grid(row=2, column=1)
cancelButton = tk.Button(master= self.quitDialog,
text='No',
command = lambda: self.quitDialog.destroy()).grid(row=2, column=2)
def start(self):
# self.invalidDiag.grab_set() #takes control over the dialog (makes it active)
self.quitDialog.wait_window()
def quitALL(self):
self.quitDialog.destroy()
self.instance.destroy()
class TimeConverter:
def __init__(self):
self.mainWindow = tk.Tk()
self.mainWindow.title("Seconds Converter")
self.results = tk.StringVar()
self.inputSecs = tk.StringVar()
secLabel = tk.Label(master=self.mainWindow,
text="Seconds:").grid(row=0, sticky="W")
resultLabel = tk.Label(master=self.mainWindow,
text="Converted Time:\n(H:M:S)").grid(row=1, sticky="W")
calcResults = tk.Label(master=self.mainWindow,
background='light gray', width=20,
textvariable=self.results,
anchor="w").grid(row=1, column=1)
secEntry = tk.Entry(master=self.mainWindow,
width=24,
textvariable=self.inputSecs).grid(row=0, column=1)
calcButton = tk.Button(master=self.mainWindow,
text='Calculate',
command=self.SecondsToHours).grid(row=2,
column=0, sticky="w")
quitButton = tk.Button(master=self.mainWindow,
text='Quit',
command=self.showQuitDialog).grid(row=3, column=1, sticky="E")
def invalidInputEntered(self):
errorDiag = AlertDialog()
errorDiag.start()
def showQuitDialog(self):
quitdialog = QuitDialog(self.mainWindow)
quitdialog.start()
def startDisplay(self) -> None:
self.mainWindow.mainloop()
def destroyit(self):
self.mainWindow.destroy()
def SecondsToHours(self):
try:
inputseconds = int(self.inputSecs.get())
seconds = int(inputseconds % 60)
minutes = int(((inputseconds - seconds) / 60) % 60)
hours = int((((inputseconds - seconds) / 60) - minutes) / 60)
tempResults = str(hours) + ':' + str(minutes) + ':' + str(seconds)
self.results.set(tempResults)
return
except ValueError:
self.invalidInputEntered()
#self.showQuitDialog()
if __name__ == '__main__':
TimeConverter().startDisplay()
Interesting question. As I know, you can also use just quit() in order to quit from program by closing everything.
quit()
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!