feat: Add spotify ascii curl page
This commit is contained in:
70
ascii_art.py
Normal file
70
ascii_art.py
Normal file
@@ -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
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
from flask import redirect, render_template, request, Blueprint, url_for
|
from flask import redirect, render_template, request, Blueprint, url_for
|
||||||
from tools import json_response, isCLI
|
from tools import json_response, isCLI
|
||||||
|
from ascii_art import image_url_to_ascii
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
import time
|
import time
|
||||||
@@ -106,7 +107,9 @@ def currently_playing():
|
|||||||
"""Public endpoint showing your current track."""
|
"""Public endpoint showing your current track."""
|
||||||
track = get_playing_spotify_track()
|
track = get_playing_spotify_track()
|
||||||
if isCLI(request):
|
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
|
# Render a simple HTML page for browsers
|
||||||
return render_template("spotify.html", track=track)
|
return render_template("spotify.html", track=track)
|
||||||
|
|||||||
21
templates/spotify.ascii
Normal file
21
templates/spotify.ascii
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{% include 'header.ascii' %}
|
||||||
|
API [/api/v1]
|
||||||
|
|
||||||
|
[1;36m───────────────────────────────────────────────[0m
|
||||||
|
[1;36m CURRENTLY PLAYING [0m
|
||||||
|
[1;36m───────────────────[0m
|
||||||
|
{% if track.error %}
|
||||||
|
Error: {{ track.error }}
|
||||||
|
{% else %}
|
||||||
|
{% if track.ascii_art %}
|
||||||
|
{{ track.ascii_art }}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
Song: [1m{{ track.song_name }}[0m
|
||||||
|
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 %}
|
||||||
Reference in New Issue
Block a user