Spaces:
Running
Running
Commit ·
72fc5ff
1
Parent(s): c84f5fa
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,22 +1,69 @@
|
|
| 1 |
# MARK: app.py
|
| 2 |
|
| 3 |
-
import os
|
| 4 |
-
import sys
|
| 5 |
-
import json
|
| 6 |
-
import base64
|
| 7 |
-
import random
|
| 8 |
from fastapi import FastAPI, Request, HTTPException
|
| 9 |
from fastapi.responses import HTMLResponse, FileResponse, StreamingResponse
|
| 10 |
from fastapi.staticfiles import StaticFiles
|
| 11 |
from fastapi.templating import Jinja2Templates
|
| 12 |
from fastapi.middleware.cors import CORSMiddleware
|
| 13 |
from pathlib import Path
|
| 14 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
from mutagen import File
|
|
|
|
|
|
|
| 16 |
from PIL import Image
|
| 17 |
-
from colorsys import
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
app = FastAPI()
|
| 21 |
|
| 22 |
app.add_middleware(
|
|
@@ -30,8 +77,7 @@ app.add_middleware(
|
|
| 30 |
SERVER_HOST = "0.0.0.0"
|
| 31 |
SERVER_PORT = 7860
|
| 32 |
BASE_URL = "https://sebastiankay-my-ai-songs.hf.space/"
|
| 33 |
-
|
| 34 |
-
GET_COVERART_URL = BASE_URL + "static/coverarts/"
|
| 35 |
|
| 36 |
|
| 37 |
# Serve static files from the "static" directory
|
|
@@ -43,67 +89,20 @@ templates = Jinja2Templates(directory="templates")
|
|
| 43 |
# Configure the base directory for your music files
|
| 44 |
app.mount("/files/music", StaticFiles(directory="files/music"), name="music")
|
| 45 |
MUSIC_DIR = Path("files/music")
|
|
|
|
| 46 |
|
| 47 |
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
# MARK: COMPLEMENTARY COLOR
|
| 55 |
-
def complementary(r, g, b):
|
| 56 |
-
"""returns RGB components of complementary color"""
|
| 57 |
-
hsv = rgb_to_hsv(r, g, b)
|
| 58 |
-
rgb_color = hsv_to_rgb((hsv[0] + 0.5) % 1, hsv[1], hsv[2])
|
| 59 |
-
return [round(rgb_color[0]), round(rgb_color[1]), round(rgb_color[2])]
|
| 60 |
-
|
| 61 |
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
img = Image.open(image_path)
|
| 65 |
-
img = img.convert("RGB")
|
| 66 |
-
img = img.resize((100, 100), resample=0)
|
| 67 |
-
pixels = list(img.getdata())
|
| 68 |
|
| 69 |
-
|
| 70 |
-
colors = []
|
| 71 |
-
for pixel in pixels:
|
| 72 |
-
r, g, b = pixel
|
| 73 |
-
h, s, v = rgb_to_hsv(r / 255, g / 255, b / 255)
|
| 74 |
-
if v > 0.5: # Filteriere hellere Farben aus
|
| 75 |
-
continue
|
| 76 |
-
if v > 0.99: # Filteriere Weiß aus
|
| 77 |
-
continue
|
| 78 |
-
colors.append((h, s, v))
|
| 79 |
-
|
| 80 |
-
# Ermittle die dominante Farbe
|
| 81 |
-
dominant_color = max(colors, key=lambda x: x[2])
|
| 82 |
-
dominant_color_rgb = hsv_to_rgb(dominant_color[0], dominant_color[1], dominant_color[2])
|
| 83 |
-
dominant_color_rgb = [int(c * 255) for c in dominant_color_rgb]
|
| 84 |
-
# print(dominant_color_rgb)
|
| 85 |
-
|
| 86 |
-
return dominant_color_rgb
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
def get_dominant_color(image, palette_size=16):
|
| 90 |
-
|
| 91 |
-
pil_img = Image.open(image)
|
| 92 |
-
|
| 93 |
-
# Resize image to speed up processing
|
| 94 |
-
img = pil_img.copy()
|
| 95 |
-
img.thumbnail((100, 100))
|
| 96 |
-
|
| 97 |
-
# Reduce colors (uses k-means internally)
|
| 98 |
-
paletted = img.convert("P", palette=Image.ADAPTIVE, colors=palette_size)
|
| 99 |
-
|
| 100 |
-
# Find the color that occurs most often
|
| 101 |
-
palette = paletted.getpalette()
|
| 102 |
-
color_counts = sorted(paletted.getcolors(), reverse=True)
|
| 103 |
-
palette_index = color_counts[0][1]
|
| 104 |
-
dominant_color = palette[palette_index * 3 : palette_index * 3 + 3]
|
| 105 |
-
|
| 106 |
-
return dominant_color
|
| 107 |
|
| 108 |
|
| 109 |
# Function to get the list of music files with metadata
|
|
@@ -116,10 +115,17 @@ def get_music_files_with_metadata():
|
|
| 116 |
music_files = []
|
| 117 |
for file in MUSIC_DIR.iterdir():
|
| 118 |
if file.is_file():
|
| 119 |
-
|
| 120 |
-
if "_HIDETHIS" in file.name:
|
|
|
|
| 121 |
continue
|
| 122 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
try: # Wrap metadata extraction in try-except
|
| 124 |
audio = File(file)
|
| 125 |
if audio is None: # Handle cases where mutagen can't read the file
|
|
@@ -129,41 +135,38 @@ def get_music_files_with_metadata():
|
|
| 129 |
track_number_str = audio.get("TRCK", ["0"])[0].split("/")[0]
|
| 130 |
track_number = int(track_number_str) if track_number_str.isdigit() else 0
|
| 131 |
|
| 132 |
-
# print(audio.getall("APIC", ["Unknown"])[0].data)
|
| 133 |
-
|
| 134 |
-
cover_art_filename = file.name + ".png" if Path("static/coverarts/" + file.name + ".png").is_file() else f"no_coverart_{random.randint(1, 4)}.png"
|
| 135 |
-
print("cover_art_path:", GET_COVERART_URL + cover_art_filename)
|
| 136 |
-
|
| 137 |
metadata = {
|
| 138 |
"encoded_title": base64.b64encode(file.name.encode("utf-8")).decode("utf-8"),
|
| 139 |
-
"filename": file.name,
|
| 140 |
"title": str(audio.get("TIT2", ["Unknown"])[0]),
|
| 141 |
"artist": str(audio.get("TPE1", ["Unknown"])[0]),
|
| 142 |
"genre": str(audio.get("TCON", ["Unknown"])[0]),
|
| 143 |
"duration": int(audio.info.length) if hasattr(audio, "info") and hasattr(audio.info, "length") else 0,
|
| 144 |
"track_number": track_number,
|
| 145 |
-
"audiofile":
|
| 146 |
-
"
|
| 147 |
-
"
|
| 148 |
-
"
|
| 149 |
-
"
|
|
|
|
|
|
|
| 150 |
}
|
| 151 |
|
| 152 |
music_files.append(metadata)
|
| 153 |
|
| 154 |
# --- Cover Art Processing ---
|
| 155 |
-
cover_art_path =
|
| 156 |
-
|
| 157 |
if cover_art_path.is_file():
|
| 158 |
try:
|
| 159 |
-
|
| 160 |
-
|
|
|
|
|
|
|
| 161 |
|
| 162 |
-
metadata["
|
| 163 |
-
metadata["
|
| 164 |
|
| 165 |
except Exception as e:
|
| 166 |
-
print(f"Error processing cover art {metadata['
|
| 167 |
else:
|
| 168 |
print(f"Cover art file not found: {cover_art_path}")
|
| 169 |
# --- End Cover Art Processing ---
|
|
@@ -215,6 +218,12 @@ async def get_file(filename: str):
|
|
| 215 |
media_type = "audio/ogg"
|
| 216 |
elif filename.lower().endswith(".flac"):
|
| 217 |
media_type = "audio/flac"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
|
| 219 |
# def iterfile(): # (1)
|
| 220 |
# with open(file_path, mode="rb") as file_like: # (2)
|
|
@@ -240,6 +249,12 @@ if __name__ == "__main__":
|
|
| 240 |
|
| 241 |
# print(f"\n\nStarting server on: \nhttp://{SERVER_HOST}:{SERVER_PORT}")
|
| 242 |
print()
|
| 243 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
print()
|
| 245 |
uvicorn.run(app, host=SERVER_HOST, port=SERVER_PORT)
|
|
|
|
| 1 |
# MARK: app.py
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
from fastapi import FastAPI, Request, HTTPException
|
| 4 |
from fastapi.responses import HTMLResponse, FileResponse, StreamingResponse
|
| 5 |
from fastapi.staticfiles import StaticFiles
|
| 6 |
from fastapi.templating import Jinja2Templates
|
| 7 |
from fastapi.middleware.cors import CORSMiddleware
|
| 8 |
from pathlib import Path
|
| 9 |
+
from dominant_color import get_luminance, complementary, get_dominant_color, _is_grayscale, _rgb_to_hsv, hsv_to_rgb, rgb_to_hsv
|
| 10 |
+
import subprocess
|
| 11 |
+
import urllib.parse
|
| 12 |
+
import os
|
| 13 |
+
import json
|
| 14 |
from mutagen import File
|
| 15 |
+
from io import BytesIO
|
| 16 |
+
import base64
|
| 17 |
from PIL import Image
|
| 18 |
+
from colorsys import rgb_to_hls, hls_to_rgb
|
| 19 |
+
import logging
|
| 20 |
+
import wave
|
| 21 |
+
import struct
|
| 22 |
+
import math
|
| 23 |
+
import random
|
| 24 |
+
import shutil
|
| 25 |
+
|
| 26 |
+
# command = "cls"
|
| 27 |
+
# if os.name in ("nt", "dos"): # If Machine is running on Win OS, use cls
|
| 28 |
+
# subprocess.call(command, shell=True)
|
| 29 |
+
# else: # If Machine is running on OSX/Linux
|
| 30 |
+
# subprocess.call(["printf", "\033c"])
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
no_style = "\033[0m"
|
| 34 |
+
bold = "\033[91m"
|
| 35 |
+
black = "\033[30m"
|
| 36 |
+
grey = "\033[90m"
|
| 37 |
+
white = "\033[97m"
|
| 38 |
+
white_bg = "\033[107m"
|
| 39 |
+
blue = "\033[94m"
|
| 40 |
+
blue_bg = "\033[104m"
|
| 41 |
+
green = "\033[92m"
|
| 42 |
+
green_bg = "\033[102m"
|
| 43 |
+
yellow = "\033[93m"
|
| 44 |
+
red = "\033[31m"
|
| 45 |
+
red_light = "\033[91m"
|
| 46 |
+
red_light_bg = "\033[101m"
|
| 47 |
+
|
| 48 |
+
global logger
|
| 49 |
+
logger = logging.getLogger()
|
| 50 |
+
handler = logging.StreamHandler()
|
| 51 |
+
logging.addLevelName(logging.DEBUG, grey + "DEBUG: " + no_style)
|
| 52 |
+
logging.addLevelName(logging.INFO, blue + "ℹ️")
|
| 53 |
+
logging.addLevelName(logging.WARNING, no_style + yellow + "WARNING:")
|
| 54 |
+
logging.addLevelName(logging.ERROR, no_style + red_light_bg + black + "ERROR: " + no_style)
|
| 55 |
+
logging.addLevelName(logging.CRITICAL, f"{no_style + red}CRITICAL:{no_style}")
|
| 56 |
+
handler.setLevel(logging.INFO) # DEBUG INFO WARNING ERROR CRITICAL
|
| 57 |
+
# formatter = logging.Formatter(fmt=" %(levelname)-8s %(message)s", style="%")
|
| 58 |
+
# formatter = logging.Formatter(fmt=" %(levelname)-8s %(message)s")
|
| 59 |
+
# # formatter = AnsiColorFormatter("{levelname:<8s} {message}", style="{")
|
| 60 |
+
formatter = logging.Formatter("{levelname:s} {message}", style="{")
|
| 61 |
+
handler.setFormatter(formatter)
|
| 62 |
+
logger.addHandler(handler)
|
| 63 |
+
logger.setLevel(logging.INFO) # DEBUG INFO WARNING ERROR CRITICAL
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
# MARK: FastAPI
|
| 67 |
app = FastAPI()
|
| 68 |
|
| 69 |
app.add_middleware(
|
|
|
|
| 77 |
SERVER_HOST = "0.0.0.0"
|
| 78 |
SERVER_PORT = 7860
|
| 79 |
BASE_URL = "https://sebastiankay-my-ai-songs.hf.space/"
|
| 80 |
+
GET_FILE_URL = BASE_URL + "getfile/"
|
|
|
|
| 81 |
|
| 82 |
|
| 83 |
# Serve static files from the "static" directory
|
|
|
|
| 89 |
# Configure the base directory for your music files
|
| 90 |
app.mount("/files/music", StaticFiles(directory="files/music"), name="music")
|
| 91 |
MUSIC_DIR = Path("files/music")
|
| 92 |
+
MUSIC_DIR_2 = Path("files/music_2")
|
| 93 |
|
| 94 |
|
| 95 |
+
def create_peaks_json(file):
|
| 96 |
+
input_file = file
|
| 97 |
+
output_file = f"{file}.json"
|
| 98 |
+
command = f'audiowaveform -i "{input_file}" -o "{output_file}" -b 8'
|
| 99 |
+
# print("Executing command:", command)
|
| 100 |
+
subprocess.call(command, shell=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
+
with open(output_file, "r") as f:
|
| 103 |
+
peaks = json.load(f)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
+
return peaks["data"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
|
| 108 |
# Function to get the list of music files with metadata
|
|
|
|
| 115 |
music_files = []
|
| 116 |
for file in MUSIC_DIR.iterdir():
|
| 117 |
if file.is_file():
|
| 118 |
+
|
| 119 |
+
if "_HIDETHIS" in file.name or file.name.endswith(".json") or file.name.endswith(".png"):
|
| 120 |
+
print(f"{yellow}Ignoring File: {no_style} {file.name}")
|
| 121 |
continue
|
| 122 |
|
| 123 |
+
filename_without_mp3 = file.name.rsplit(".", 1)[0]
|
| 124 |
+
filename_coverart = filename_without_mp3 + ".coverart.png"
|
| 125 |
+
filename_coverart_encoded = urllib.parse.quote(filename_coverart)
|
| 126 |
+
filename_peaks = filename_without_mp3 + ".peaks.json"
|
| 127 |
+
# peaks = create_peaks_json(file)
|
| 128 |
+
|
| 129 |
try: # Wrap metadata extraction in try-except
|
| 130 |
audio = File(file)
|
| 131 |
if audio is None: # Handle cases where mutagen can't read the file
|
|
|
|
| 135 |
track_number_str = audio.get("TRCK", ["0"])[0].split("/")[0]
|
| 136 |
track_number = int(track_number_str) if track_number_str.isdigit() else 0
|
| 137 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
metadata = {
|
| 139 |
"encoded_title": base64.b64encode(file.name.encode("utf-8")).decode("utf-8"),
|
|
|
|
| 140 |
"title": str(audio.get("TIT2", ["Unknown"])[0]),
|
| 141 |
"artist": str(audio.get("TPE1", ["Unknown"])[0]),
|
| 142 |
"genre": str(audio.get("TCON", ["Unknown"])[0]),
|
| 143 |
"duration": int(audio.info.length) if hasattr(audio, "info") and hasattr(audio.info, "length") else 0,
|
| 144 |
"track_number": track_number,
|
| 145 |
+
"audiofile": GET_FILE_URL + file.name,
|
| 146 |
+
"coverart": GET_FILE_URL + filename_coverart_encoded,
|
| 147 |
+
"dominant_color_1": "128, 128, 128",
|
| 148 |
+
"complementary_color_1": "128, 128, 128",
|
| 149 |
+
"dominant_color_2": "128, 128, 128",
|
| 150 |
+
"complementary_color_2": "128, 128, 128",
|
| 151 |
+
# "peaks": peaks,
|
| 152 |
}
|
| 153 |
|
| 154 |
music_files.append(metadata)
|
| 155 |
|
| 156 |
# --- Cover Art Processing ---
|
| 157 |
+
cover_art_path = MUSIC_DIR / filename_coverart
|
|
|
|
| 158 |
if cover_art_path.is_file():
|
| 159 |
try:
|
| 160 |
+
colors = get_dominant_color(cover_art_path, min_brightness=0.4, max_brightness=0.8, n_clusters=4)
|
| 161 |
+
|
| 162 |
+
metadata["dominant_color_1"] = ", ".join(map(str, colors[0]))
|
| 163 |
+
metadata["complementary_color_1"] = ", ".join(map(str, colors[1]))
|
| 164 |
|
| 165 |
+
metadata["dominant_color_2"] = ", ".join(map(str, colors[2]))
|
| 166 |
+
metadata["complementary_color_2"] = ", ".join(map(str, colors[3]))
|
| 167 |
|
| 168 |
except Exception as e:
|
| 169 |
+
print(f"{red_light}Error{no_style} processing cover art {metadata['coverart']}: {e}")
|
| 170 |
else:
|
| 171 |
print(f"Cover art file not found: {cover_art_path}")
|
| 172 |
# --- End Cover Art Processing ---
|
|
|
|
| 218 |
media_type = "audio/ogg"
|
| 219 |
elif filename.lower().endswith(".flac"):
|
| 220 |
media_type = "audio/flac"
|
| 221 |
+
elif filename.lower().endswith(".png"):
|
| 222 |
+
media_type = "image/png"
|
| 223 |
+
elif filename.lower().endswith(".jpg") or filename.lower().endswith(".jpeg"):
|
| 224 |
+
media_type = "image/jpeg"
|
| 225 |
+
elif filename.lower().endswith(".gif"):
|
| 226 |
+
media_type = "image/gif"
|
| 227 |
|
| 228 |
# def iterfile(): # (1)
|
| 229 |
# with open(file_path, mode="rb") as file_like: # (2)
|
|
|
|
| 249 |
|
| 250 |
# print(f"\n\nStarting server on: \nhttp://{SERVER_HOST}:{SERVER_PORT}")
|
| 251 |
print()
|
| 252 |
+
logger.info("=============================================================")
|
| 253 |
+
logger.info(" ")
|
| 254 |
+
logger.info(f"API DOCS: {BASE_URL}docs")
|
| 255 |
+
logger.info(f"ENDPOINT: {BASE_URL}api/music")
|
| 256 |
+
logger.info("")
|
| 257 |
+
logger.info("=============================================================")
|
| 258 |
+
print()
|
| 259 |
print()
|
| 260 |
uvicorn.run(app, host=SERVER_HOST, port=SERVER_PORT)
|