0

I've got my home made music player up and running. Now it is time to add some album artwork extracted from currently playing song with ffmpeg. I've done lots of searching but am not sure the most efficient way of setting this up in tkinter. A rough window mock-up:

+================+
|                |   Current Artist: Best Band Ever
|  (No Artwork)  |   Current Album:  Greatest Album Ever
|                |   Current Song:   Irresistible Tune
|                |   Play Progress:  99.9 seconds of: 999
+================+

At the bottom of the window are buttons; Close, Pause, Previous, Next.

When grabbing window border to enlarge, I'd like the picture to resize the most as initial size is 200x200 which is hard to see.

My question is: How do I define the image variable in the TK label field?

Temporarily I have setup the image as a TK string var:

    ''' Artwork image spanning 4 rows '''
    self.current_song_artwork = tk.StringVar()
    tk.Label(master2, text="(No Artwork)", font=(None, MON_FONTSIZE)) \
             .grid(row=0, rowspan=4, column=0, sticky=tk.W)

NOTE 1: I've seen this Q&A: How to update the image of a Tkinter Label widget? which uses:

img2 = ImageTk.PhotoImage(Image.open(path2))
panel.configure(image=img2)
panel.image = img2

and this seems like the only option?

NOTE 2: When the toplevel window is first opened there is no song playing and no image to display for artwork. Some sort of placeholder will be required?

NOTE 3: Down the road I'd like options to replace artwork with music visualizer (scaled down), scrolling lyrics, or scrolling biography from wikipedia. So future-proofing with a robust design makes sense today.

NOTE 4: This is only my second Python/Tkinter program and much of the code below was copied and pasted from the first. The first in turn was mainly code copied and pasted from Stack Overflow. Any tips on reducing code lines or improving efficiency would be greatly appreciated!


TL;DR

The complete (not very exciting code) is:

def play_items(self):
    ''' Play 1 or more songs in listbox.selection()
        "Close", "Next", "Prev" and "Pause" buttons
        
        Selection may be artist, album or random songs. Get item tag:

        Auto-starts playing first song with:
            ffplay -nodisp -autoexit "/path/to/song.ext"
        If Pause button pressed change text to "Play" and issue:
            PID with pgrep ffplay then use kill -s STOP <PID> to suspend
        If Play button pressed change text to "Pause" and issue:
            PID with pgrep ffplay then use kill -s CONT <PID> to resume

    '''

    if len(self.listbox.selection()) == 0 :
        # TODO: Dialog "Select one or more songs to play."
        return

    ''' Make parent buttons invisible so user doesn't try to click '''
    self.clear_buttons()

    self.top2 = tk.Toplevel()
    self.top2_is_active = True

    ''' Place Window top-left of parent window with PANEL_HGT padding '''
    xy = (self.toplevel.winfo_x() + PANEL_HGT, \
          self.toplevel.winfo_y() + PANEL_HGT)
    self.top2.minsize(width=BTN_WID * 10, height=PANEL_HGT * 4)
    self.top2.geometry('+%d+%d'%(xy[0], xy[1]))
    self.top2.title("Playing Selected Songs")
    self.top2.configure(background="Gray")
    self.top2.columnconfigure(0, weight=1)
    self.top2.rowconfigure(0, weight=1)

    ''' Create master frame '''
    master2 = tk.Frame(self.top2, borderwidth=BTN_BRD_WID, relief=tk.RIDGE)
    master2.grid(sticky=tk.NSEW)

    ''' Artwork image spanning 4 rows '''
    self.current_song_artwork = tk.StringVar()
    tk.Label(master2, text="(No Artwork)", font=(None, MON_FONTSIZE)) \
             .grid(row=0, rowspan=4, column=0, sticky=tk.W)

    ''' Current artist '''
    self.current_song_artist = tk.StringVar()
    tk.Label(master2, text="Current Artist:", font=(None, MON_FONTSIZE)) \
             .grid(row=0, column=1, sticky=tk.W)
    tk.Label(master2, text="", textvariable=self.current_song_artist, \
             font=(None, MON_FONTSIZE)).grid(row=0, column=2, \
                                             sticky=tk.W)
    ''' Current album '''
    self.current_song_album = tk.StringVar()
    tk.Label(master2, text="Current Album:", font=(None, MON_FONTSIZE)) \
             .grid(row=1, column=1, sticky=tk.W)
    tk.Label(master2, text="", textvariable=self.current_song_album, \
             font=(None, MON_FONTSIZE)).grid(row=1, column=2, \
                                             sticky=tk.W)
    ''' Current song '''
    self.current_song_path = ""
    self.current_song_name = tk.StringVar()
    tk.Label(master2, text="Current Song:", font=(None, MON_FONTSIZE)) \
             .grid(row=2, column=1, sticky=tk.W)
    tk.Label(master2, text="", textvariable=self.current_song_name, \
             font=(None, MON_FONTSIZE)).grid(row=2, column=2, \
                                             sticky=tk.W)
    ''' Progress of play '''
    self.current_progress = tk.StringVar()
    tk.Label(master2, text="Play Progress:", font=(None, MON_FONTSIZE)) \
             .grid(row=3, column=1, sticky=tk.W)
    tk.Label(master2, text="", textvariable=self.current_progress, \
             font=(None, MON_FONTSIZE)).grid(row=3, column=2, \
                                             sticky=tk.W)

    ''' Frame for Buttons '''
    frame3 = tk.Frame(self.top2, bg="Blue", borderwidth=BTN_BRD_WID, \
                      relief=tk.GROOVE)
    frame3.grid(row=2, column=0, sticky=tk.NSEW)
    frame3.grid_rowconfigure(0, weight=1)
    frame3.grid_columnconfigure(0, weight=0)
    button = tk.Button(frame3, text="✘ Close", width=BTN_WID, \
                        command=self.play_close)
    button.grid(row=0, column=0, padx=2, sticky=tk.W)

    ''' Close Button '''
    self.top2.bind("<Escape>", self.play_close)
    self.top2.protocol("WM_DELETE_WINDOW", self.play_close)

    ''' Pause/Play Button '''
    self.pp_state = "Playing"     
    self.pp_play_text = "▶  Play"
    self.pp_pause_text = "❚❚ Pause"
    self.pp_button_text = self.pp_pause_text
    self.pp_button = tk.Button(frame3, text=self.pp_button_text, \
                        width=BTN_WID, command=self.pp_toggle)
    self.pp_button.grid(row=0, column=1, padx=2)

    ''' Next/Prev Button '''
    # U+1f844          U+1f846 
    # U_1f808          I+1f80a 
    prevbutton = tk.Button(frame3, text="  Previous", width=BTN_WID, \
                           command=lambda s=self: s.get_setting('prev'))
    prevbutton.grid(row=0, column=2, padx=2, sticky=tk.W)
    nextbutton = tk.Button(frame3, text="Next  ", width=BTN_WID, \
                           command=lambda s=self: s.get_setting('next'))
    nextbutton.grid(row=0, column=3, padx=2, sticky=tk.W)

    ''' Start at first listbox entry seleected '''
    self.get_setting('ZERO')            # Set self.ndx to 0
    self.listbox.update_idletasks()
    self.play_forever()
WinEunuuchs2Unix
  • 1,801
  • 1
  • 17
  • 34

1 Answers1

1

First of all, what a really well put together comprehensive question. This will be helpful for you and future google-searchers.

This is doable, but there's a few gotchas to be careful of, namely saving to the class the image and its outer label. I've got a simple example here that will run, placing and displaying an image on a label.

import tkinter as tk
from PIL import Image,ImageTk

class Player:
    def __init__(self):
        self.root = tk.Tk()
        self.artwork_label = tk.Label()
        self.artwork_label.pack()
        self.img = ImageTk.PhotoImage(Image.open(path))
        self.artwork_label.configure(image=self.img)


path = ...
player = Player()

Edit something like this into yours, and if you're still stuck, comment and I'll help you out some more.

JimmyCarlos
  • 1,934
  • 1
  • 10
  • 24
  • Firstly thank you for kind words. Secondly, I read `.pack()` was bad so it never appears in my program. Consequently I have more `.grid()`'s than mosquitoes in the forest. Lastly, it will take some time to digest this divergence to my existing code so answer acceptance will be tardy. – WinEunuuchs2Unix Jul 27 '20 at 01:15
  • You're right: .grid() is much better than .pack(), in nearly every situation. – JimmyCarlos Jul 27 '20 at 01:17