Spaces:
Running
Running
| from flask import Flask, request, render_template_string, send_from_directory | |
| import requests, os, re, glob, uuid | |
| from mutagen.easyid3 import EasyID3 | |
| app = Flask(__name__) | |
| DOWNLOAD_FOLDER = "downloads" | |
| os.makedirs(DOWNLOAD_FOLDER, exist_ok=True) | |
| MAX_FILES = 20 | |
| TEMPLATE = """ | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <title>Udio Library</title> | |
| <style> | |
| body { background:#111; color:white; padding:2rem; font-family:sans-serif; } | |
| audio { width:100%; margin-top:1rem; } | |
| canvas { border:1px solid #333; width:100%; height:200px; margin-top:1rem; } | |
| ul { list-style:none; padding:0; } | |
| li { margin:0.5rem 0; } | |
| button.play-btn { margin-right:0.5rem; } | |
| #drop-area { border:2px dashed #555; padding:1rem; margin:1rem 0; text-align:center; } | |
| footer { margin-top:3rem; padding-top:2rem; border-top:1px solid #444; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>Udio Music Library</h1> | |
| <ul id="playlist"> | |
| {% for song in songs %} | |
| <li data-url="{{ song.url }}"> | |
| <button class="play-btn" onclick="playSong('{{ song.url }}')">▶️</button> | |
| {{ song.title }} | |
| </li> | |
| {% endfor %} | |
| </ul> | |
| <audio id="audio" controls crossorigin="anonymous"></audio> | |
| <canvas id="vis" width="800" height="200"></canvas> | |
| <div id="drop-area"> | |
| Drag & Drop MP3 files here to upload | |
| </div> | |
| <footer> | |
| <h2>Fetch new song from Udio embed or song URL</h2> | |
| <form method="POST"> | |
| <input type="text" name="embed_url" placeholder="Enter song page, embed URL or iframe snippet" size="60"> | |
| <button type="submit">Fetch</button> | |
| </form> | |
| <ul> | |
| {% for msg in messages %} | |
| <li>{{ msg }}</li> | |
| {% endfor %} | |
| </ul> | |
| <p style="margin-top:2rem; font-size:0.8rem;">Built with ❤️ by <a href="https://chat.openai.com" target="_blank" style="color:#ccc;">ChatGPT</a></p> | |
| </footer> | |
| <script> | |
| const audio = document.getElementById('audio'); | |
| const canvas = document.getElementById('vis'); | |
| const cctx = canvas.getContext('2d'); | |
| const ctx = new (window.AudioContext || window.webkitAudioContext)(); | |
| const src = ctx.createMediaElementSource(audio); | |
| const analyser = ctx.createAnalyser(); | |
| analyser.fftSize = 256; | |
| src.connect(analyser); | |
| analyser.connect(ctx.destination); | |
| const bufferLength = analyser.frequencyBinCount; | |
| const dataArray = new Uint8Array(bufferLength); | |
| function draw() { | |
| requestAnimationFrame(draw); | |
| analyser.getByteFrequencyData(dataArray); | |
| cctx.fillStyle = "#000"; | |
| cctx.fillRect(0, 0, canvas.width, canvas.height); | |
| const barWidth = (canvas.width / bufferLength) * 2.5; | |
| let x = 0; | |
| for (let i = 0; i < bufferLength; i++) { | |
| const barHeight = dataArray[i]; | |
| cctx.fillStyle = `rgb(${barHeight+100},50,50)`; | |
| cctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight); | |
| x += barWidth + 1; | |
| } | |
| } | |
| draw(); | |
| audio.addEventListener('play', async () => await ctx.resume()); | |
| let playlist = Array.from(document.querySelectorAll("#playlist li")).map(li => li.dataset.url); | |
| let currentIndex = 0; | |
| function playSong(url) { | |
| audio.src = url; | |
| audio.play(); | |
| currentIndex = playlist.indexOf(url); | |
| } | |
| audio.addEventListener("ended", () => { | |
| currentIndex++; | |
| if (currentIndex >= playlist.length) currentIndex = 0; | |
| playSong(playlist[currentIndex]); | |
| }); | |
| // Drag & Drop Upload | |
| let dropArea = document.getElementById('drop-area'); | |
| dropArea.addEventListener('dragover', (e) => { e.preventDefault(); dropArea.style.borderColor = '#fff'; }); | |
| dropArea.addEventListener('dragleave', (e) => { dropArea.style.borderColor = '#555'; }); | |
| dropArea.addEventListener('drop', async (e) => { | |
| e.preventDefault(); | |
| dropArea.style.borderColor = '#555'; | |
| const files = e.dataTransfer.files; | |
| for(let file of files){ | |
| if(file.type === "audio/mpeg"){ | |
| let formData = new FormData(); | |
| formData.append('file', file); | |
| await fetch('/upload', { method:'POST', body: formData }); | |
| location.reload(); | |
| } | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| def normalize_udio_input(input_text): | |
| """Accept song page URL, embed URL, or iframe snippet, return embed URL""" | |
| match = re.search(r'src=["\'](https://www\.udio\.com/embed/[^"\']+)["\']', input_text) | |
| if match: | |
| return match.group(1) | |
| if input_text.startswith("https://www.udio.com/embed/"): | |
| return input_text.split()[0] | |
| song_match = re.search(r'https://www\.udio\.com/songs/([A-Za-z0-9]+)', input_text) | |
| if song_match: | |
| song_id = song_match.group(1) | |
| return f"https://www.udio.com/embed/{song_id}" | |
| return None | |
| def get_mp3_from_embed(embed_url): | |
| messages = [] | |
| mp3_url = "" | |
| messages.append(f"Fetching embed page: {embed_url}") | |
| try: | |
| r = requests.get(embed_url) | |
| html = r.text | |
| mp3_match = re.search(r'https://storage\.googleapis\.com/.*?\.mp3', html) | |
| if mp3_match: | |
| mp3_url = mp3_match.group(0) | |
| messages.append(f"Found MP3 URL: {mp3_url}") | |
| else: | |
| messages.append("MP3 URL not found in embed page.") | |
| except Exception as e: | |
| messages.append(f"Error fetching embed page: {e}") | |
| return messages, mp3_url | |
| def download_mp3(mp3_url): | |
| ext = ".mp3" | |
| unique_name = f"{uuid.uuid4()}{ext}" | |
| local_path = os.path.join(DOWNLOAD_FOLDER, unique_name) | |
| try: | |
| r = requests.get(mp3_url) | |
| with open(local_path, "wb") as f: | |
| f.write(r.content) | |
| except Exception: | |
| return None | |
| return f"/downloads/{unique_name}" | |
| def list_songs(): | |
| songs = [] | |
| for f in sorted(os.listdir(DOWNLOAD_FOLDER)): | |
| if f.lower().endswith(".mp3"): | |
| path = os.path.join(DOWNLOAD_FOLDER, f) | |
| title = f | |
| try: | |
| audio = EasyID3(path) | |
| if 'title' in audio: | |
| title = audio['title'][0] | |
| except: | |
| pass | |
| songs.append({"title": title, "url": f"/downloads/{f}"}) | |
| return songs | |
| def cleanup_old_files(): | |
| files = sorted(glob.glob(os.path.join(DOWNLOAD_FOLDER, "*.mp3")), key=os.path.getmtime) | |
| while len(files) > MAX_FILES: | |
| os.remove(files[0]) | |
| files.pop(0) | |
| def index(): | |
| messages = [] | |
| if request.method == "POST": | |
| user_input = request.form.get("embed_url") | |
| if user_input: | |
| embed_url = normalize_udio_input(user_input) | |
| if embed_url: | |
| msg, mp3_url = get_mp3_from_embed(embed_url) | |
| messages.extend(msg) | |
| if mp3_url: | |
| download_mp3(mp3_url) | |
| messages.append("Downloaded new MP3.") | |
| cleanup_old_files() | |
| else: | |
| messages.append("Could not extract embed URL from input.") | |
| songs = list_songs() | |
| return render_template_string(TEMPLATE, messages=messages, songs=songs) | |
| def serve_file(filename): | |
| return send_from_directory(DOWNLOAD_FOLDER, filename) | |
| def upload_file(): | |
| file = request.files.get("file") | |
| if file and file.filename.endswith(".mp3"): | |
| ext = ".mp3" | |
| unique_name = f"{uuid.uuid4()}{ext}" | |
| file.save(os.path.join(DOWNLOAD_FOLDER, unique_name)) | |
| cleanup_old_files() | |
| return "", 204 | |
| def serve_download(filename): | |
| return send_from_directory(DOWNLOAD_FOLDER, filename) | |
| if __name__ == "__main__": | |
| app.run(debug=True) | |