Spaces:
Runtime error
Runtime error
| #!/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() | |