I have amended your code to demo how to link your slider to the track. See below.
To update the slider's position, you need to familiarize yourself with :
- tkinter's
.get()
and .set()
methods (see here) and use the variable option of the tkinter Scale
widget (see here ).
- tkinter's
.after()
method (see here) to create a event loop to track the playing of the mp3 file. The .PlayTrack()
function that I have created demo how to do this.
- Reading Pygame's mixer
documentation,
I realised
.get_pos()
and .set_pos()
operates in milliseconds and
seconds respectively, hence you need to be careful with the
conversions.
Point 1: Note there is an issue in the amended code. The code is able to play the music file from the start. However, if the slide is not in the start point, pressing the Play button will raise an exception/error
line 24, in Play mixer.music.set_pos( playtime )
pygame.error: set_pos unsupported for this codec
.
I am not sure why .set_pos
did not work. I will leave you to resolve this. Pls share your answer after you have solve the issue. All the best.
Point 2: I place mixer.init()
in the Play()
function instead of in the main code because I noticed that once it is activated, an entire CPU is consumed. i thought activating it after the Play button is pressed could help you conserve your compute resource.
Amended Code:
#using python3.6
from tkinter import *
from pygame import mixer
root = Tk()
from mutagen.mp3 import MP3
#fLen =MP3('A Message to you Bit.mp3')
musicfile='Test.mp3'
fLen =MP3( musicfile )
FileLength = fLen.info.length
print('FileLength = ', FileLength, ' sec')
#load & play an mp3 file from root dir
def Play():
print('\ndef Play():')
mixer.init()
mixer.music.load( musicfile )
mixer.music.set_volume( .25 )
playtime = slider_value.get()
if playtime > 0:
print( 'playtime = ', playtime, type(playtime) )
mixer.music.rewind()
mixer.music.set_pos( playtime )
mixer.music.play()
TrackPlay()
def TrackPlay():
if mixer.music.get_busy():
current = mixer.music.get_pos() #.get_pos() returns integer in milliseconds
print( 'current = ', current, type(current) )
slider_value.set( current/1000 ) #.set_pos() works in seconds
print( 'slider_value = ', slider_value.get(), type(slider_value.get()) )
root.after(1000, lambda:TrackPlay() ) # Loop every sec
#set slider to mp3 file position
def ProgressBar( value ):
print('\ndef ProgressBar( value ):')
print('value = ', value, type(value))
slider_value.set( value )
print('slider_value.get() = ', slider_value.get(), type(slider_value.get()) )
print('value = ', value, type(value) )
#slider.configure(from_=slider_value.get())
#create widgets
playBut = Button(text='Play',command=Play)
playBut.pack()
slider_value = DoubleVar()
slider = Scale( to=FileLength, orient=HORIZONTAL, length=500, resolution=1,
showvalue=True, tickinterval=30, variable=slider_value,
command=ProgressBar)
slider.pack()
root.mainloop()
Update 1:
I took time to look more into the outstanding issue. While doing so, I realised a few issues awaiting us.
To play a track at a defined time, we can use pygame.mixer.music.play( start=time )
. time
is an argument we need to provide. Don't need to use the pygame.mixer.music.set_pos()
method which is giving us problem.
Pygame's advice:
Be aware that MP3 support is limited. On some systems an unsupported
format can crash the program, e.g. Debian Linux. Consider using OGG
instead.
MP3 don't go well with pygame so we should look to using OGG.
For the music tracker to be useful, it should allow a user to reposition the slider while a track is playing, and the play will start from the new slider position. To do this, we will have to cancel the callback create by the .after
method. To address such an issue, writing the python application in an object oriented manner will make things easier for us.
We need a way to quit pygame.mixer before the entire Tk window is destroyed. If not, it will continue to run in the background and consume an entire CPU core.
Based on the above, I wrote a new script. I found it to work very well for an ogg vorbis track but not for a mp3 track. I have commented the python script to what I am doing in the script. Hope it can be a help you learn how to use tkinter and python to do what you want. It's my first go at using pygame so please pardon me if my answer on pygame is inadequate.
To use this file, make the necessary change to lines 42, 44, and 146.
New Code:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from mutagen.mp3 import MP3
from mutagen.oggvorbis import OggVorbis
from mutagen import MutagenError
from pygame import mixer
import tkinter as tk
import tkinter.messagebox as tkMessageBox
class MusicPlayer( tk.Frame ):
def __init__(self, master, tracktype='ogg', *args, **kwargs):
super().__init__(master) #initilizes self, which is a tk.Frame
self.pack()
# MusicPlayer's Atrributes
self.master = master # Tk window
self.track = None # Audio file
self.trackLength = None # Audio file length
self.player = None # Music player
self.playBut = None # Play Button
self.stopBut = None # Stop Button
self.slider = None # Progress Bar
self.slider_value = None # Progress Bar value
# Call these methods
self.get_AudioFile_MetaData( tracktype )
self.load_AudioFile()
self.create_Widgets()
def get_AudioFile_MetaData( self, tracktype ):
'''Get audio file and it's meta data (e.g. tracklength).'''
print( '\ndef get_AudioFileMetaData( self, audiofile ):' )
try:
if tracktype == 'mp3':
audiofile='Test.mp3' # In current directory
f = MP3( audiofile )
elif tracktype == 'ogg':
audiofile='Test.ogg' # In current directory
f = OggVorbis( audiofile )
else:
raise print( 'Track type not supported.' )
except MutagenError:
print( "Fail to load audio file ({}) metadata".format(audiofile) )
else:
trackLength = f.info.length
self.track = audiofile
self.trackLength = trackLength; print( 'self.trackLength',type(self.trackLength),self.trackLength,' sec' )
def load_AudioFile( self ):
'''Initialise pygame mixer, load audio file and set volume.'''
print( '\ndef load_AudioFile( self, audiofile ):' )
player = mixer
player.init()
player.music.load( self.track )
player.music.set_volume( .25 )
self.player = player
print('self.player ', self.player)
def create_Widgets ( self ):
'''Create Buttons (e.g. Start & Stop ) and Progress Bar.'''
print( '\ndef create_Widgets ( self ):' )
self.playBut = tk.Button( self, text='Play', command=self.Play )
self.playBut.pack()
self.stopBut = tk.Button( self, text='Stop', command=self.Stop )
self.stopBut.pack()
self.slider_value = tk.DoubleVar()
self.slider = tk.Scale( self, to=self.trackLength, orient=tk.HORIZONTAL, length=700,
resolution=0.5, showvalue=True, tickinterval=30, digit=4,
variable=self.slider_value, command=self.UpdateSlider )
self.slider.pack()
def Play( self ):
'''Play track from slider location.'''
print('\ndef Play():')
#1. Get slider location.
#2. Play music from slider location.
#3. Update slider location (use tk's .after loop)
playtime = self.slider_value.get(); print( type(playtime),'playtime = ',playtime,'sec' )
self.player.music.play( start=playtime ); print( 'Play Started' )
self.TrackPlay( playtime )
def TrackPlay( self, playtime ):
'''Slider to track the playing of the track.'''
print('\ndef TrackPlay():')
#1.When track is playing
# 1. Set slider position to playtime
# 2. Increase playtime by interval (1 sec)
# 3. start TrackPlay loop
#2.When track is not playing
# 1. Print 'Track Ended'
if self.player.music.get_busy():
self.slider_value.set( playtime ); print( type(self.slider_value.get()),'slider_value = ',self.slider_value.get() )
playtime += 1.0
self.loopID = self.after(1000, lambda:self.TrackPlay( playtime ) );\
print( 'self.loopID = ', self.loopID )
else:
print('Track Ended')
def UpdateSlider( self, value ):
'''Move slider position when tk.Scale's trough is clicked or when slider is clicked.'''
print( '\ndef UpdateSlider():' ); print(type(value),'value = ',value,' sec')
if self.player.music.get_busy():
print("Track Playing")
self.after_cancel( self.loopID ) #Cancel PlayTrack loop
self.slider_value.set( value ) #Move slider to new position
self.Play( ) #Play track from new postion
else:
print("Track Not Playing")
self.slider_value.set( value ) #Move slider to new position
def Stop( self ):
'''Stop the playing of the track.'''
print('\ndef Stop():')
if self.player.music.get_busy():
self.player.music.stop()
print('Play Stopped')
def ask_quit():
'''Confirmation to quit application.'''
if tkMessageBox.askokcancel("Quit", "Exit MusicPlayer"):
app.Stop() #Stop playing track
app.player.quit() #Quit pygame.mixer
root.destroy() #Destroy the Tk Window instance.
# Note: After initialzing pygame.mixer, it will preoccupy an entire CPU core.
# Before destroying the Tk Window, ensure pygame.mixer is quitted too else
# pygame.mixer will still be running in the background despite destroying the
# Tk Window instance.
if __name__ == "__main__":
root = tk.Tk() #Initialize an instance of Tk window.
app = MusicPlayer( root, tracktype='ogg' ) #Initialize an instance of MusicPlayer object and passing Tk window instance into it as it's master.
root.protocol("WM_DELETE_WINDOW", ask_quit) #Tell Tk window instance what to do before it is destroyed.
root.mainloop() #Start Tk window instance's mainloop.