#!/usr/bin/env python3 """ Gradio Demo for SongBPM Scraper A web-based interface to search and display song BPM and musical key information. Run with: python gradio_app.py Then open http://localhost:7860 in your browser. """ import asyncio import gradio as gr from gradio import processing_utils import os import pandas as pd from pathlib import Path from songbpm_scraper import SongBPMExtractor, SongData # Global extractor instance _extractor = None def get_extractor(): """Get or create the extractor instance.""" global _extractor if _extractor is None: _extractor = SongBPMExtractor(headless=True) return _extractor def search_song(query: str) -> str: """ Search for a single song and return formatted results. Args: query: Song search query (artist - song title) Returns: Formatted results string """ if not query or not query.strip(): return "Please enter a song to search for." query = query.strip() extractor = get_extractor() try: results = asyncio.run(extractor.extract(query)) if not results: return f"No results found for '{query}'.\n\nTips:\n- Try the format: 'artist - song title'\n- Be more specific with the artist name\n- Check for spelling errors" # Format results output_lines = [f"Found {len(results)} result(s):\n"] for i, song in enumerate(results, 1): output_lines.append(f"{i}. **{song.get('artist', 'Unknown')}** - {song.get('title', 'Unknown')}") output_lines.append(f" - BPM: {song.get('bpm', 'N/A')}") output_lines.append(f" - Key: {song.get('key', 'N/A')}") output_lines.append(f" - Duration: {song.get('duration', 'N/A')}") output_lines.append(f" - [Source]({song.get('url', '#')})") output_lines.append("") return "\n".join(output_lines) except Exception as e: return f"Error searching for '{query}':\n{str(e)}" def search_batch(file_obj) -> str: """ Search for multiple songs from a file. Args: file_obj: Gradio file object containing song queries Returns: Formatted results string """ if file_obj is None: return "Please upload a file with song queries." try: # Read the file if hasattr(file_obj, 'name'): with open(file_obj.name, 'r', encoding='utf-8') as f: queries = [line.strip() for line in f if line.strip()] else: # Handle Gradio's file object format processing_utils.decode_base64_to_file(file_obj) return "File upload processing not yet implemented. Please try again." if not queries: return "The file appears to be empty." extractor = get_extractor() results = asyncio.run(extractor.extract_batch(queries)) if not results: return f"No results found for any of the {len(queries)} songs in the file." # Format results output_lines = [f"Found {len(results)} result(s) from {len(queries)} queries:\n"] for i, song in enumerate(results, 1): output_lines.append(f"{i}. **{song.get('artist', 'Unknown')}** - {song.get('title', 'Unknown')}") output_lines.append(f" BPM: {song.get('bpm', 'N/A')} | Key: {song.get('key', 'N/A')} | Duration: {song.get('duration', 'N/A')}") output_lines.append("") output_lines.append(f"---") output_lines.append(f"Processed {len(queries)} queries, found {len(results)} results.") return "\n".join(output_lines) except Exception as e: return f"Error processing file: {str(e)}" def search_and_export(query: str, export_format: str) -> tuple: """ Search for songs and optionally export results. Args: query: Song search query export_format: Export format ('csv', 'json', or 'none') Returns: Tuple of (results_text, file_path) """ if not query or not query.strip(): return ("Please enter a song to search for.", None) query = query.strip() extractor = get_extractor() try: results = asyncio.run(extractor.extract(query)) if not results: return (f"No results found for '{query}'.", None) # Format results for display output_lines = [f"Found {len(results)} result(s):\n"] for i, song in enumerate(results, 1): output_lines.append(f"{i}. **{song.get('artist', 'Unknown')}** - {song.get('title', 'Unknown')}") output_lines.append(f" BPM: {song.get('bpm', 'N/A')} | Key: {song.get('key', 'N/A')} | Duration: {song.get('duration', 'N/A')}") results_text = "\n".join(output_lines) # Export if requested file_path = None if export_format != 'none' and results: filename = f"songbpm_results" if export_format == 'csv': file_path = f"{filename}.csv" extractor.export_to_csv(results, file_path) elif export_format == 'json': file_path = f"{filename}.json" extractor.export_to_json(results, file_path) return (results_text, file_path) except Exception as e: return (f"Error: {str(e)}", None) def clear_results() -> tuple: """Clear all inputs and outputs.""" return "", "", "none", None # Create the Gradio interface def create_demo(): """Create and configure the Gradio demo.""" with gr.Blocks(title="SongBPM Scraper") as demo: gr.Markdown( """ # SongBPM Scraper Search for songs and get their BPM (tempo) and musical key information. --- **Data provided by:** [songbpm.com](https://songbpm.com) *Note: Please be patient between searches to respect the website's resources.* """ ) with gr.Tab("Single Search"): gr.Markdown("### Search for a Single Song") with gr.Row(): with gr.Column(scale=3): query_input = gr.Textbox( label="Song Query", placeholder="e.g., queen - under pressure", ) with gr.Column(scale=1): search_btn = gr.Button("Search", variant="primary") export_radio = gr.Radio( choices=["none", "csv", "json"], label="Export Results", value="none" ) result_output = gr.Markdown(label="Results") file_output = gr.File(label="Download", visible=False) search_btn.click( fn=search_and_export, inputs=[query_input, export_radio], outputs=[result_output, file_output] ) query_input.submit( fn=search_and_export, inputs=[query_input, export_radio], outputs=[result_output, file_output] ) with gr.Tab("Batch Search"): gr.Markdown("### Search for Multiple Songs") file_input = gr.File( label="Upload File", file_types=[".txt", ".csv"] ) batch_search_btn = gr.Button("Search All Songs", variant="primary") batch_result_output = gr.Markdown(label="Results") batch_search_btn.click( fn=search_batch, inputs=file_input, outputs=batch_result_output ) with gr.Tab("Help"): gr.Markdown( """ ## How to Use ### Single Search 1. Enter a song query in the text box 2. Use the format: `artist - song title` 3. Click "Search" or press Enter 4. View results below ### Batch Search 1. Create a text file with one query per line 2. Upload the file 3. Click "Search All Songs" ### Examples ``` queen - under pressure daft punk - one more time metallica - enter sandman taylor swift - shake it off ``` ## Tips - Use the artist name followed by a dash and the song title - Be specific with artist names for better results - Check spelling if you don't get results - Results include BPM, musical key, and duration ## Export Options - **CSV**: Download as spreadsheet-compatible file - **JSON**: Download as structured data file - **None**: Just view results in the browser """ ) gr.Markdown( """ --- *Built with Python, Playwright, and Gradio* **Disclaimer**: This tool is for educational purposes only. Please respect songbpm.com's terms of service. """ ) return demo def main(): """Main entry point for the Gradio app.""" import argparse parser = argparse.ArgumentParser(description="Run the SongBPM Scraper Gradio Demo") parser.add_argument( "--host", type=str, default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)" ) parser.add_argument( "--port", type=int, default=7860, help="Port to bind to (default: 7860)" ) parser.add_argument( "--share", action="store_true", help="Create a public share link" ) parser.add_argument( "--debug", action="store_true", help="Enable debug mode" ) args = parser.parse_args() print(f""" ╔════════════════════════════════════════════════════════════╗ ║ SongBPM Scraper - Gradio Demo ║ ╠════════════════════════════════════════════════════════════╣ ║ ║ ║ Starting server... ║ ║ ║ ║ Local URL: http://{args.host}:{args.port} ║ ║ ║ ║ Press Ctrl+C to stop the server ║ ║ ║ ╚════════════════════════════════════════════════════════════╝ """) demo = create_demo() demo.launch( server_name=args.host, server_port=args.port, share=args.share, debug=args.debug, theme="NeoPy/Soft", show_error=True ) if __name__ == "__main__": main()