Posts: 1,145
Threads: 114
Joined: Sep 2019
#! /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()
Posts: 1,145
Threads: 114
Joined: Sep 2019
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
Posts: 1,838
Threads: 2
Joined: Apr 2017
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?
Posts: 1,145
Threads: 114
Joined: Sep 2019
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.
Posts: 1,838
Threads: 2
Joined: Apr 2017
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.
|