diff --git a/ascii_art.py b/ascii_art.py new file mode 100644 index 0000000..63ee34a --- /dev/null +++ b/ascii_art.py @@ -0,0 +1,70 @@ +import requests +from PIL import Image +from io import BytesIO + +ASCII_CHARS = ["@", "#", "S", "%", "?", "*", "+", ";", ":", ",", "."] + + +def resized_gray_image(image, new_width=40): + """ + Resize and convert image to grayscale. + """ + width, height = image.size + aspect_ratio = height / width + # 0.55 is a correction factor as terminal characters are taller than they are wide + new_height = int(aspect_ratio * new_width * 0.55) + img = image.resize((new_width, new_height)) + return img.convert("L") + + +def pixels_to_ascii(image): + """ + Map grayscale pixels to ASCII characters. + """ + pixels = image.getdata() + # 255 / 11 (len(ASCII_CHARS)) ~= 23. Using 25 for safe integer division mapping. + characters = "".join([ASCII_CHARS[pixel // 25] for pixel in pixels]) + return characters + + +def image_url_to_ascii(url, new_width=40): + """ + Convert an image URL to a colored ASCII string using ANSI escape codes. + """ + if not url: + return "" + + try: + response = requests.get(url, timeout=5) + image = Image.open(BytesIO(response.content)) + except Exception: + return "" + + # Resize image + width, height = image.size + aspect_ratio = height / width + # Calculate new height to maintain aspect ratio, considering terminal character dimensions + # ASCII chars are taller than they are wide (approx ~2x) + # Since we are using '██' (double width), we effectively make each "cell" square. + # So we can just scale by aspect ratio directly without additional correction factor. + new_height = int(aspect_ratio * new_width) + if new_height > 60: + new_height = 60 + new_width = int(new_height / aspect_ratio) + + # Resize and ensure RGB mode + img = image.resize((new_width, new_height)) + img = img.convert("RGB") + + pixels = img.getdata() + + ascii_str = "" + for i, pixel in enumerate(pixels): + r, g, b = pixel + ascii_str += f"\033[38;2;{r};{g};{b}m██\033[0m" + + # Add newline at the end of each row + if (i + 1) % new_width == 0: + ascii_str += "\n" + + return ascii_str diff --git a/blueprints/spotify.py b/blueprints/spotify.py index 48e326f..41df344 100644 --- a/blueprints/spotify.py +++ b/blueprints/spotify.py @@ -1,5 +1,6 @@ from flask import redirect, render_template, request, Blueprint, url_for from tools import json_response, isCLI +from ascii_art import image_url_to_ascii import os import requests import time @@ -106,7 +107,9 @@ def currently_playing(): """Public endpoint showing your current track.""" track = get_playing_spotify_track() if isCLI(request): - return json_response(request, {"spotify": track}, 200) + if "album_art" in track: + track["ascii_art"] = image_url_to_ascii(track["album_art"], new_width=40) + return render_template("spotify.ascii", track=track) # Render a simple HTML page for browsers return render_template("spotify.html", track=track) diff --git a/templates/spotify.ascii b/templates/spotify.ascii new file mode 100644 index 0000000..3db01eb --- /dev/null +++ b/templates/spotify.ascii @@ -0,0 +1,21 @@ +{% include 'header.ascii' %} +API [/api/v1] + +─────────────────────────────────────────────── + CURRENTLY PLAYING  +─────────────────── +{% if track.error %} +Error: {{ track.error }} +{% else %} +{% if track.ascii_art %} +{{ track.ascii_art }} + +{% endif %} +Song: {{ track.song_name }} +Artist: {{ track.artist }} +Album: {{ track.album_name }} +URL: {{ track.url }} + +Progress: {{ track.progress_ms // 60000 }}:{{ '%02d' % ((track.progress_ms // 1000) % 60) }} / {{ track.duration_ms // 60000 }}:{{ '%02d' % ((track.duration_ms // 1000) % 60) }} +Status: {{ 'Playing' if track.is_playing else 'Paused' }} +{% endif %}