SongBPM / app.py
NeoPy's picture
Update app.py
c836b36 verified
#!/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()