Как связать mp3-файл с ползунком, чтобы ползунок перемещался относительно длины mp3-файла? - PullRequest
0 голосов
/ 07 января 2019

Как связать mp3-файл с ползунком, чтобы ползунок перемещался относительно длины mp3-файла? В моем коде ползунок перемещается, только если я удерживаю мышь на впадине ползунка. Как сделать так, чтобы мышь не удерживалась постоянно?

#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()

1 Ответ

0 голосов
/ 08 января 2019

Я изменил ваш код, чтобы продемонстрировать, как связать ваш слайдер с треком. Увидеть ниже. Чтобы обновить положение слайдера, вам необходимо ознакомиться с:

  • методы tkinter .get() и .set() (см. здесь ) и использование параметра переменной виджета tkinter Scale (см. здесь ).
  • Метод .after() tkinter (см. здесь ), чтобы создать цикл обработки событий для отслеживания воспроизведения mp3-файла. Функция .PlayTrack(), которую я создал, демонстрирует, как это сделать.
  • Чтение Микшер Pygame документация , Я понял, .get_pos() и .set_pos() работает в миллисекундах и секунд, следовательно, вы должны быть осторожны с преобразования.

Пункт 1: Обратите внимание, что в исправленном коде есть проблема. Код может воспроизводить музыкальный файл с самого начала. Однако, если слайд не находится в начальной точке, нажатие кнопки «Воспроизведение» вызовет исключение / ошибку

line 24, in Play mixer.music.set_pos( playtime ) pygame.error: set_pos unsupported for this codec.

Я не уверен, почему .set_pos не работал. Я оставлю вас, чтобы решить это. Просьба поделиться своим ответом после того, как вы решите проблему. Всего наилучшего.

Точка 2: Я помещаю mixer.init() в функцию Play() вместо основного кода, потому что я заметил, что, как только он активирован, используется весь ЦП. Я думал, что активация после нажатия кнопки «Воспроизведение» может помочь вам сэкономить вычислительные ресурсы.

Измененный код:

#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()

Обновление 1:

Мне потребовалось время, чтобы больше разобраться в нерешенной проблеме. При этом я осознал несколько проблем, ожидающих нас.

  1. Для воспроизведения дорожки в определенное время мы можем использовать pygame.mixer.music.play( start=time ). time это аргумент, который мы должны предоставить. Не нужно использовать метод pygame.mixer.music.set_pos(), который доставляет нам проблемы.

  2. Совет Пигейма :

    Помните, что поддержка MP3 ограничена. На некоторых системах неподдерживаемый формат может привести к сбою программы, например, Debian Linux. Рассмотрите возможность использования OGG вместо этого.

    MP3 плохо сочетается с Pygame, поэтому мы должны использовать OGG.

  3. Чтобы музыкальный трекер был полезен, он должен позволять пользователю перемещать слайдер во время воспроизведения трека, и воспроизведение начнется с новой позиции слайдера. Для этого нам придется отменить обратный вызов, созданный методом .after. Чтобы решить эту проблему, написание приложения на Python объектно-ориентированным способом облегчит нам задачу.

  4. Нам нужен способ выйти из pygame.mixer до того, как все окно Tk будет уничтожено. Если нет, он будет продолжать работать в фоновом режиме и использовать все ядро ​​процессора.

Исходя из вышеизложенного, я написал новый скрипт. Я нашел, что это работает очень хорошо для трека ogg vorbis, но не для трека mp3. Я прокомментировал скрипт Python к тому, что я делаю в скрипте. Надеюсь, что это поможет вам научиться использовать tkinter и python, чтобы делать то, что вы хотите. Это мой первый опыт использования pygame, поэтому, пожалуйста, извините, если мой ответ на pygame неадекватен.

Чтобы использовать этот файл, внесите необходимые изменения в строки 42, 44 и 146.

Новый код:

#!/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.
...