1

How do I link an mp3 file with the slider so that the slider moves in relation to the mp3 file length? In my code the slider only moves if I hold the mouse down on the slider trough. How do I make this work without the mouse being held down all the time?

#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')
FileLength = fLen.info.length * 1000
mixer.init()

#load & play an mp3 file from root dir
def Play():
    mixer.music.load('A Message to you Bit.mp3')
    mixer.music.set_volume(.25)
    mixer.music.play()

#set slider to mp3 file position
def ProgressBar(event):
    slider.set(mixer.music.get_pos())

#create widgets
playBut = Button(text='Play',command = Play)
playBut.pack()

slider = Scale(to=FileLength, orient=HORIZONTAL, command=ProgressBar)
slider.pack()
DaiBach
  • 59
  • 9

1 Answers1

3

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.
Sun Bear
  • 7,594
  • 11
  • 56
  • 102
  • @DaiBach, pls see Update 1 in Answer. I think it works much better now. On Ubuntu 16.04, tested using ogg track. The mp3 track did not perform well except it can only play. If you try other features, the program can hang or quit. What kind of OS are you using? – Sun Bear Jan 09 '19 at 09:36
  • This now works very well. Many thanks. There is so much new in the code that it'll be ages before I get my head around it. – DaiBach Jan 11 '19 at 15:02
  • Great. Kindly click on the "Tick" symbol found near the top left corner of this answer, this will mean this question is answered. Pointers: (1) Learn to write your python program in an object orient way (using class and methods), this can allow you to do more for a GUI. (2) The tkinter documentation I had referred you to, and reading the question and answers by other SOFers, are good sources to learn tkinter methods. (3) Simply your problem where u can to learn. For this question, you have to contend with python, tkinter, mutagen, pygame. :) Did you test code in Win, MacOS or Linux? Thanks. – Sun Bear Jan 14 '19 at 12:18
  • I tested the code on Windows 7 and Ubuntu 18. Not yest got round to trying it on my wife's Mac. How would I add a Menu item so that I could select any file. I know how to code the Menu but can't get it to work with the new code as I don't yet understand Classes. – DaiBach Jan 14 '19 at 19:25
  • For the topic on having a Menu to open file, you have to open a new question. But I am sure you can google the answer to your question. – Sun Bear Jan 15 '19 at 03:14