Dynamically scale an image within Tkinter when changing window size or to full screen?

51 views Asked by At

I've made a python script for a raspberry pi that:

  • records audio for 4 seconds.
  • uses Shazam via Shazamio to find the song title and artist and cover art.
  • makes a window using Tkinter and shows the cover art, then title and then artist below (with text styling and background colouring based on the most dominant colour of the cover art).
  • updates every 10 seconds after completing the above.

Everything works great when in a window: windowed mode

When changing to full screen it looks like this: full screen mode (taking screenshots wouldn't work on my raspberry pi, hence the photos).

I cannot figure out how make the image dynamically scale or resize larger to fill the space when resizing to a larger window or when switching to full screen, also cannot figure out how to make the spacing/padding between the text remain the same or similar to when in windowed mode.

No widgets should overlay, and I've set the minimum size of the window to be the original size the cover art + the song details.

I've tested changing the adjust_layout function, along with the zoom and subsample methods in ImageTk.PhotoImage and the adjust_layout function, but to no avail. Please see the below latest attempt of making the image 75% the size of the larger window whilst maintaining the aspect ratio:

import wave
import concurrent.futures
import tkinter as tk
import asyncio
from shazamio import Shazam
import requests
from PIL import Image, ImageTk, ImageColor
import io
import numpy as np
import colorsys
import math
import os

# Constants
RECORD_SECS = 4
MAX_RETRIES = 5
CHUNK = 8192

original_width = 0
original_height = 0
current_image = None

async def record_and_recognize(audio):
    print("Recording started...")
    form_1 = pyaudio.paInt16  
    chans = 1  
    samp_rate = 48000  
    dev_index = 2  

    script_dir = os.path.dirname(os.path.abspath(__file__))

    wav_output_filename = os.path.join(script_dir, 'shazam.wav')  

    stream = audio.open(format=form_1, rate=samp_rate, channels=chans, \
                      input_device_index=dev_index, input=True, \
                       frames_per_buffer=CHUNK)

    frames = []

    for _ in range(0, int((samp_rate / CHUNK) * RECORD_SECS)):

        data = stream.read(CHUNK)

        frames.append(data)

    stream.stop_stream()

    stream.close()

    wavefile = wave.open(wav_output_filename, 'wb')

    wavefile.setnchannels(chans)
 wavefile.setsampwidth(audio.get_sample_size(form_1))

    wavefile.setframerate(samp_rate)

    wavefile.writeframes(b''.join(frames))

    wavefile.close()

    print("Recording completed...")

    shazam = Shazam()

    retry_count = 0

    while retry_count < MAX_RETRIES:

        try:

            print("Recognizing the song...")

            result = await shazam.recognize(wav_output_filename)

            print("Recognition completed.")

            break  # Exit the loop if successful

        except (requests.RequestException, Exception) as e:

            print(f"Failed to recognize the song. Retrying... (Attempt {retry_count + 1}/{MAX_RETRIES})")

            retry_count += 1

            await asyncio.sleep(2)  # Wait for a short duration before retrying

    if retry_count == MAX_RETRIES:

        print("Max retry attempts reached. Could not recognize the song.")

        return

    if 'track' in result:

        track = result['track']

        track_title = track['title']

        artist_name = track['subtitle']

        if 'images' in track:

            cover_art_url = track['images']['coverarthq']

            response = requests.get(cover_art_url)

            image_data = response.content

            image = Image.open(io.BytesIO(image_data))

            global original_width, original_height, current_image

            original_width, original_height = image.size

            resized_image = image.resize((original_width, original_height))

            current_image = ImageTk.PhotoImage(resized_image)

            dominant_color = find_dominant_color(resized_image)

            lighter_color = lighten_color(dominant_color, 1)
            root.configure(bg=lighter_color)
           cover_art_label.config(image=current_image, bg=lighter_color)

            cover_art_label.image = current_image  

            text_color = get_text_color(lighter_color)
           title_label.config(text=track_title, fg=text_color, bg=lighter_color, font=("Arial", 16, "bold"))
           artist_label.config(text=artist_name, fg=text_color, bg=lighter_color, font=("Arial", 16, "italic"))

        else:

            print("No cover art available for this track.")

    else:

        print("Could not recognize the song.")

def update_song_information(audio):

    print("Updating song information...")

   asyncio.run(record_and_recognize(audio))

    root.after(10000, update_song_information, audio)

def find_dominant_color(image):

    img_array = np.array(image)

    flat_array = img_array.reshape(-1, img_array.shape[-1])

    unique_colors, counts = np.unique(flat_array, axis=0, return_counts=True)

    dominant_index = np.argmax(counts)

    dominant_color = tuple(unique_colors[dominant_index])

    hex_color = '#{0:02x}{1:02x}{2:02x}'.format(*dominant_color)

    return hex_color

def lighten_color(color, factor=0.5):

    r, g, b = ImageColor.getcolor(color, "RGB")

    h, l, s = colorsys.rgb_to_hls(r / 255.0, g / 255.0, b / 255.0)

    l = min(1.0, l * factor)

    r, g, b = colorsys.hls_to_rgb(h, l, s)

    return '#{0:02x}{1:02x}{2:02x}'.format(int(r * 255), int(g * 255), int(b * 255))

def get_text_color(bg_color):

    r, g, b = ImageColor.getcolor(bg_color, "RGB")

    brightness = math.sqrt(0.299 * r**2 + 0.587 * g**2 + 0.114 * b**2)

    if brightness < 128:

        return "white"

    else:

        return "black"

def adjust_layout(event=None):

    if root.attributes("-fullscreen"):
       cover_art_label.grid_configure(pady=50)

        title_label.grid_configure(pady=(30, 0))

        artist_label.grid_configure(pady=(10, 20))

        root.update_idletasks()

        screen_width = root.winfo_screenwidth()

        screen_height = root.winfo_screenheight()

        cover_art_width = min(int(screen_width * 0.75), original_width)

        cover_art_height = min(int(screen_height * 0.75), original_height)
        cover_art_label.config(width=cover_art_width, height=cover_art_height)

        if current_image:

            resized_image = current_image.zoom(cover_art_width // original_width, cover_art_height // original_height)
           cover_art_label.config(image=resized_image)

            cover_art_label.image = resized_image

    else:
       cover_art_label.grid_configure(pady=(30, 0))

        title_label.grid_configure(pady=(10, 0))

        artist_label.grid_configure(pady=(10, 20))

        root.update_idletasks()
        cover_art_label.config(width=original_width, height=original_height)

        if current_image:
          cover_art_label.config(image=current_image)

            cover_art_label.image = current_image

root = tk.Tk()

root.title("Song Recognition")

# Set to fullscreen by default

root.attributes("-fullscreen", True)

def toggle_fullscreen(event=None):

    root.attributes("-fullscreen", not root.attributes("-fullscreen"))

    adjust_layout()

root.bind("<Escape>", toggle_fullscreen)

cover_art_label = tk.Label(root)

title_label = tk.Label(root, text="", font=("Arial", 16, "bold"))

artist_label = tk.Label(root, text="", font=("Arial", 16, "italic"))

audio = pyaudio.PyAudio()

update_song_information(audio)

# Use grid geometry manager

cover_art_label.grid(row=0, column=0, padx=10, pady=(30, 0), sticky="nsew")

title_label.grid(row=1, column=0, pady=(10, 0), sticky="nsew")

artist_label.grid(row=2, column=0, pady=(10, 20), sticky="nsew")

# Configure rows and columns to expand

root.grid_rowconfigure(0, weight=1)

root.grid_rowconfigure(1, weight=1)

root.grid_rowconfigure(2, weight=1)

root.grid_columnconfigure(0, weight=1)

# Bind the adjust_layout function to the Configure event of the root window

root.bind("<Configure>", adjust_layout)

root.update_idletasks()

root.mainloop()
0

There are 0 answers