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