Basic Music Player with tkinter - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: General (https://python-forum.io/forum-1.html) +--- Forum: Code sharing (https://python-forum.io/forum-5.html) +--- Thread: Basic Music Player with tkinter (/thread-34289.html) |
Basic Music Player with tkinter - menator01 - Jul-15-2021 #! /usr/bin/env python3 import tkinter as tk import pygame as pg from os import listdir, chdir from functools import partial import re class MusicPlayer: def __init__(self, parent): self.parent = parent self.parent.columnconfigure(0, weight=1, uniform='cols') self.parent.columnconfigure(1, weight=1, uniform='cols') self.parent.rowconfigure(0, weight=1) self.mytext = None pg.init() pg.mixer.init() track_label_frame = tk.LabelFrame(self.parent, text='Track') track_label_frame.grid(column=0, row=0, sticky='new') for i in range(2): track_label_frame.grid_columnconfigure(i, weight=3, uniform='labels') self.label = tk.Label(track_label_frame, text=None, bg='lightblue') self.label['relief'] = 'groove' self.label.grid(column=1, row=0, sticky='new') self.status = tk.Label(track_label_frame, text=None, bg='lightblue') self.status['relief'] = 'groove' self.status.grid(column=0, row=0, sticky='new') btn_label_frame = tk.LabelFrame(self.parent, text='Controls') btn_label_frame.grid(column=0, row=1, sticky='new') for i in range(4): btn_label_frame.grid_columnconfigure(i, weight=3, uniform='btns') self.btn_play = tk.Button(btn_label_frame, text='Play') self.btn_play['command'] = partial(self.play, state=0) self.btn_play.grid(column=0, row=0, sticky='new', padx=3, pady=2) btn_stop = tk.Button(btn_label_frame, text='Stop') btn_stop['command'] = partial(self.play, state=None) btn_stop.grid(column=1, row=0, sticky='new', padx=3, pady=2) btn_next = tk.Button(btn_label_frame, text='Next') btn_next['command'] = partial(self.nextsong) btn_next.grid(column=2, row=0, sticky='new', padx=3, pady=2) track_list_frame = tk.LabelFrame(self.parent, text='Track List') track_list_frame.grid(column=1, row=0, rowspan=2, sticky='news') track_list_frame.grid_columnconfigure(0, weight=3) track_list_frame.grid_rowconfigure(0, weight=3) scrollbar = tk.Scrollbar(track_list_frame, orien='vertical') self.playlist = tk.Listbox(track_list_frame, yscrollcommand=scrollbar.set, \ selectbackground='lightblue', selectforeground='navy', selectmode='single', width=50) scrollbar.grid(column=1, row=0, sticky='news') scrollbar.config(command=self.playlist.yview) self.playlist.grid(column=0, row=0, sticky='news') chdir('My Music') songs = listdir() for song in songs: self.playlist.insert(tk.END, song) def play(self, state): if state == 0: display_label = self.playlist.get(tk.ACTIVE)[:-4] patt = r'\w+\s\w+' label = ' '.join(re.findall(patt, display_label)) self.label['text'] = label self.status['text'] = 'Now Playing' pg.mixer.music.load(self.playlist.get(tk.ACTIVE)) pg.mixer.music.play() self.btn_play['text'] = 'Pause' self.btn_play['command'] = partial(self.play, state=1) if not self.playlist.curselection(): self.playlist.select_set(0) else: self.playlist.select_set(self.playlist.curselection()[0]) elif state == 1: self.status['text'] = 'Paused' pg.mixer.music.pause() self.btn_play['text'] = 'Play' self.btn_play['command'] = partial(self.play, state=2) elif state == 2: self.status['text'] = 'Now Playing' pg.mixer.music.unpause() self.btn_play['text'] = 'Pause' self.btn_play['command'] = partial(self.play, state=1) else: self.status['text'] = 'Stopped' pg.mixer.music.stop() self.btn_play['command'] = partial(self.play, state=0) self.btn_play['text'] = 'Play' def nextsong(self): pg.mixer.music.stop() select_index = self.playlist.curselection() next_select = 0 if len(select_index) > 0: last_select = int(select_index[-1]) self.playlist.selection_clear(select_index) if last_select < self.playlist.size() - 1: next_select = last_select + 1 self.playlist.activate(next_select) self.playlist.selection_set(next_select) self.play(state=0) def main(): root = tk.Tk() root.title('Tkinter Music Player') MusicPlayer(root) root.mainloop() main() RE: Basic Music Player with tkinter - menator01 - Jul-26-2021 Version 2 has a few tweaks. Still to do - make a play all option and clean up the code. #! /usr/bin/env python3 import pygame as pg import tkinter as tk from tkinter import filedialog, messagebox from os import chdir, listdir, sys from functools import partial PAUSE = pg.USEREVENT+1 TRACK_END = pg.USEREVENT+1 class MusicFolder: def __init__(self): self.folder = None def get_files(self, tracklist, *args, **kwargs): self.folder = kwargs['folder'] tracklist.delete(0, tk.END) chdir(self.folder) tracks = [] formats = ['mp3', 'wav', 'ogg'] playlist = listdir() for track in playlist: if track[-3:] in formats: tracklist.insert(tk.END, track) tracklist.select_set(0) class Controls: def __init__(self): pass def play(self, *args, **kwargs): file = f'{kwargs["folder"]}/{kwargs["active"]}' pg.mixer.music.set_endevent(TRACK_END) try : pg.mixer.music.load(file) pg.mixer.music.play() except Exception: messagebox.showerror(title='No folder selected', \ message='You must select a music folder to load the play list. The program will exit now.') sys.exit() def pause(self): pg.mixer.music.pause() def unpause(self): pg.mixer.music.unpause() def next(self, *args, **kwargs): pass def prev(self, *args, **kwargs): pass def stop(self, *args, **kwargs): pg.mixer.music.stop() pg.mixer.music.set_endevent() file = f'{kwargs["folder"]}/{kwargs["active"]}' pg.mixer.music.set_endevent(TRACK_END) pg.mixer.music.load(file) class Player: def __init__(self, parent): self.parent = parent self.parent.update() self.width = self.parent.winfo_width() self.height = self.parent.winfo_height() self.parent.grid_columnconfigure(0, weight=1) self.parent.grid_rowconfigure(0, weight=1) bgcolor = 'light blue' fgcolor = 'navy' self.folder = None pg.init() pg.mixer.init() pg.mixer.music.set_endevent(TRACK_END) self.control = Controls() self.music = MusicFolder() # Container container = tk.Frame(self.parent) container.grid(column=0, row=0, sticky='news') # Contains the header image/canvas headerframe = tk.Frame(container) headerframe.grid(column=0, row=0, sticky='new', pady=2, padx=2) # Contains 3 columns of info - status/track/choose folder button frame1 = tk.Frame(container) frame1.grid(column=0, row=1, sticky='new', pady=2) # Status Label self.status_label = tk.Label(frame1, padx=8, bg=bgcolor, fg=fgcolor, \ width=17, text='No status', anchor='w') self.status_label['bd'] = 1 self.status_label['relief'] = 'ridge' self.status_label.grid(column=0, row=0, sticky='news', padx=2) # Track playing label self.track_label = tk.Label(frame1, padx=8, bg=bgcolor, fg=fgcolor, \ width=70, text='No track is playing', anchor='w') self.track_label['bd'] = 1 self.track_label['relief'] = 'ridge' self.track_label.grid(column=1, row=0, sticky='news', padx=4) # Button for populating our listbox with tracks self.button = tk.Button(frame1, text='Choose Music Folder', \ fg='navy', bg='lightsteelblue') self.button['command'] = partial(self.get_music) self.button.grid(column=2, row=0, sticky='new', padx=2) self.button.bind('<Enter>', partial(self.on_enter, self.button)) self.button.bind('<Leave>', partial(self.on_exit, self.button)) # Contains 3 columns - spacer/listbox/scrollbar frame2 = tk.Frame(container) frame2.grid(column=0, row=2, sticky='new', pady=2) frame2.grid_columnconfigure(0, weight=3) # Just a spacer label. May use to show album image? spacer_label = tk.Label(frame2, bg='silver', bd=1, relief='ridge') spacer_label['height'] = 15 spacer_label['width'] = 30 spacer_label.grid(column=0, row=0, sticky='news', padx=2) # Frame for listbox to give appearence of text not against side padframe = tk.Frame(frame2, bd=1, relief='ridge', bg='aliceblue', padx=8, \ pady=5) padframe['highlightcolor'] = '#999999' padframe['highlightbackground'] = '#999999' padframe['highlightthickness'] = 1 padframe.grid(column=2, row=0, sticky='news', padx=2) padframe.grid_rowconfigure(0, weight=3) padframe.grid_columnconfigure(0, weight=3) # Listbox and scrollbar self.scrollbar = tk.Scrollbar(frame2, orient='vertical') self.playlist = tk.Listbox(padframe, width=70, bd=0, bg='aliceblue') self.playlist['yscrollcommand'] = self.scrollbar.set self.playlist['selectmode'] = 'single' self.playlist['selectbackground'] = 'lightsteelblue' self.playlist['selectforeground'] = 'navy' self.playlist['highlightcolor'] = 'white' self.playlist['highlightbackground'] = 'white' self.playlist['highlightthickness'] = 0 self.playlist['bd'] = 0 self.playlist.grid(column=0, row=0, sticky='news') self.scrollbar.grid(column=3, row=0, sticky='ns', padx=2) # Contains the control buttons - play/stop/next/prev frame3 = tk.Frame(container) frame3.grid(column=0, row=3, sticky='new', pady=2) for i in range(4): frame3.grid_columnconfigure(i, weight=3, uniform='control_btns') # The buttons - play/stop/next/prev # play button will double as a pause button self.play_btn = tk.Button(frame3, text='Play', fg='navy', bg='lightsteelblue') self.play_btn.grid(column=0, row=0, sticky='new', padx=2) self.play_btn['command'] = partial(self.play, state='play') self.play_btn.bind('<Enter>', partial(self.on_enter, self.play_btn)) self.play_btn.bind('<Leave>', partial(self.on_exit, self.play_btn)) self.stop_btn = tk.Button(frame3, text='Stop', fg='navy', bg='lightsteelblue') self.stop_btn.grid(column=1, row=0, sticky='new', padx=2) self.stop_btn['command'] = partial(self.play, state='stop') self.stop_btn.bind('<Enter>', partial(self.on_enter, self.stop_btn)) self.stop_btn.bind('<Leave>', partial(self.on_exit, self.stop_btn)) self.next_btn = tk.Button(frame3, text='Next', fg='navy', bg='lightsteelblue') self.next_btn.grid(column=2, row=0, sticky='new', padx=2) self.next_btn['command'] = partial(self.next) self.next_btn.bind('<Enter>', partial(self.on_enter, self.next_btn)) self.next_btn.bind('<Leave>', partial(self.on_exit, self.next_btn)) self.back_btn = tk.Button(frame3, text='Prev', fg='navy', bg='lightsteelblue') self.back_btn.grid(column=3, row=0, sticky='new', padx=2) self.back_btn['command'] = partial(self.prev) self.back_btn.bind('<Enter>', partial(self.on_enter, self.back_btn)) self.back_btn.bind('<Leave>', partial(self.on_exit, self.back_btn)) # self.update() # # def update(self): # for event in pg.event.get(): # if event.type == TRACK_END: # self.play_btn['text'] = 'Play' # self.status_label['text'] = 'No status' # self.track_label['text'] = 'No track is playing' # pass # # self.parent.after(100, self.update) def get_music(self): self.folder = filedialog.askdirectory() self.music.get_files(self.playlist, folder=self.folder) # Define some button animations def on_enter(self, btn, event): btn['bg'] = 'powderblue' btn['fg'] = 'navy' btn['cursor'] = 'hand2' def on_exit(self, btn, event): btn['bg'] = 'lightsteelblue' btn['fg'] = 'navy' def next(self): pg.mixer.music.stop() index = self.playlist.curselection() if index: next_index = 0 if len(index) > 0: last_index = int(index[-1]) self.playlist.selection_clear(index) if last_index < self.playlist.size()-1: next_index = last_index + 1 self.playlist.activate(next_index) self.playlist.selection_set(next_index) self.status_label['text'] = 'Now Playing here' self.play(state='play') else: pass def prev(self): try: pg.mixer.music.stop() index = self.playlist.curselection() last_index = int(index[-1]) if last_index == 0: last_index = self.playlist.size() self.playlist.selection_clear(index) last_index = last_index - 1 self.playlist.activate(last_index) self.playlist.selection_set(last_index) self.play(state='play') except Exception: pass def play(self, *args, **kwargs): if self.playlist.get(tk.ACTIVE): state = kwargs['state'] self.track_label['text'] = self.playlist.get(tk.ACTIVE)[:-4] if state == 'play': self.play_btn['text'] = 'Pause' self.play_btn['command'] = partial(self.play, state='pause') self.status_label['text'] = 'Now Playing' self.control.play(active=self.playlist.get(tk.ACTIVE), folder=self.folder) elif state == 'pause': self.play_btn['text'] = 'Resume' self.play_btn['command'] = partial(self.play, state='unpause') self.status_label['text'] = 'Paused' self.control.pause() elif state == 'unpause': self.play_btn['text'] = 'Pause' self.play_btn['command'] = partial(self.play, state='pause') self.status_label['text'] = 'Now Playing' self.control.unpause() else: try: index = self.playlist.curselection() self.play_btn['text'] = 'Play' self.play_btn['command'] = partial(self.play, state='play') self.status_label['text'] = 'No status' self.track_label['text'] = 'No track is playing' self.playlist.selection_clear(index) self.playlist.select_set(0) self.playlist.activate(0) self.control.stop(folder=self.folder, active=self.playlist.get(tk.ACTIVE)) except Exception: pass else: messagebox.showerror(title='No folder selected.', message='Please choose a folder with music files.') pass def main(): root = tk.Tk() root.title('Tkinter Music Player') root.geometry('805x315+250+250') root.resizable(0, 0) root['padx'] = 10 root['pady'] = 5 Player(root) root.mainloop() if __name__ == '__main__': main() # pg.mixer.music.load(f'{folder}/{playlist[0]}') # pg.mixer.music.play() # playlist.pop(0) # # running = True # while running: # TRACK_END = pg.USEREVENT+1 # pg.mixer.music.set_endevent(TRACK_END) # # for event in pg.event.get(): # if event.type == TRACK_END: # if len(playlist) > 0: # pg.mixer.music.load(f'{folder}/{playlist[0]}') # playlist.pop(0) # pg.mixer.music.play() # else: # running = False # break RE: Basic Music Player with tkinter - ndc85430 - Jul-30-2021 I haven't looked at all the code, but why is Controls a class? There's no state it encapsulates that the methods share. Couldn't those methods just be functions inside a module instead?
RE: Basic Music Player with tkinter - menator01 - Jul-30-2021 yes. I'm just playing around with classes. It's even easier getting the code to work if it's just a function or methods in the same class. RE: Basic Music Player with tkinter - ndc85430 - Jul-31-2021 Just try and use classes appropriately - they aren't always the right way to group related functionality, as in this case. I'm not sure what you mean by "easier to get the code to work" by using a class there. The functions are completely independent of one another, so if they were in a module, you'd just have to import them and use them. |