- Introduced a new `html_report.py` file to generate an HTML report for albums to add and update. - Implemented a `generate_html_report` function that creates a styled HTML document with clickable submission links. - Integrated the new report generation function into `main.py` to streamline the process of reporting missing albums. - Enhanced user experience with filtering options for album types and artists in the generated report.
358 lines
12 KiB
Python
358 lines
12 KiB
Python
"""HTML report generation for missing albums"""
|
|
|
|
from html import escape
|
|
from typing import Dict, List
|
|
|
|
|
|
def generate_html_report(albums_to_add: List[Dict], albums_to_update: List[Dict]):
|
|
"""Generate an HTML report with clickable submission links"""
|
|
html_content = """<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>MusicBrainz Albums - Add & Update</title>
|
|
<style>
|
|
body {{
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
background-color: #f5f5f5;
|
|
}}
|
|
h1 {{
|
|
color: #333;
|
|
border-bottom: 3px solid #4CAF50;
|
|
padding-bottom: 10px;
|
|
}}
|
|
h2 {{
|
|
color: #2196F3;
|
|
margin-top: 30px;
|
|
border-bottom: 2px solid #2196F3;
|
|
padding-bottom: 5px;
|
|
}}
|
|
.album {{
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin: 20px 0;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}}
|
|
.album-title {{
|
|
font-size: 1.5em;
|
|
font-weight: bold;
|
|
color: #2196F3;
|
|
margin-bottom: 10px;
|
|
}}
|
|
.artist-name {{
|
|
color: #666;
|
|
margin-bottom: 15px;
|
|
}}
|
|
.links {{
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}}
|
|
.link-button {{
|
|
display: inline-block;
|
|
padding: 10px 20px;
|
|
background-color: #4CAF50;
|
|
color: white;
|
|
text-decoration: none;
|
|
border-radius: 5px;
|
|
transition: background-color 0.3s;
|
|
}}
|
|
.link-button:hover {{
|
|
background-color: #45a049;
|
|
}}
|
|
.link-button.atisket {{
|
|
background-color: #2196F3;
|
|
}}
|
|
.link-button.atisket:hover {{
|
|
background-color: #0b7dda;
|
|
}}
|
|
.link-button.harmony {{
|
|
background-color: #FF9800;
|
|
}}
|
|
.link-button.harmony:hover {{
|
|
background-color: #e68900;
|
|
}}
|
|
.deezer-link {{
|
|
color: #666;
|
|
font-size: 0.9em;
|
|
margin-top: 10px;
|
|
}}
|
|
.mb-link {{
|
|
color: #666;
|
|
font-size: 0.9em;
|
|
margin-top: 5px;
|
|
}}
|
|
.issues {{
|
|
color: #FF9800;
|
|
font-size: 0.9em;
|
|
margin-top: 5px;
|
|
font-style: italic;
|
|
}}
|
|
.summary {{
|
|
background: white;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
margin-bottom: 20px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}}
|
|
.filter-buttons {{
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
flex-wrap: wrap;
|
|
}}
|
|
.filter-button {{
|
|
padding: 10px 20px;
|
|
border: 2px solid #2196F3;
|
|
background-color: white;
|
|
color: #2196F3;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
font-size: 1em;
|
|
transition: all 0.3s;
|
|
}}
|
|
.filter-button:hover {{
|
|
background-color: #2196F3;
|
|
color: white;
|
|
}}
|
|
.filter-button.active {{
|
|
background-color: #2196F3;
|
|
color: white;
|
|
}}
|
|
.album-section {{
|
|
display: none;
|
|
}}
|
|
.album-section.visible {{
|
|
display: block;
|
|
}}
|
|
.filter-container {{
|
|
display: flex;
|
|
gap: 20px;
|
|
margin-bottom: 20px;
|
|
flex-wrap: wrap;
|
|
align-items: flex-start;
|
|
}}
|
|
.filter-group {{
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}}
|
|
.filter-group label {{
|
|
font-weight: bold;
|
|
color: #333;
|
|
font-size: 0.9em;
|
|
}}
|
|
.artist-buttons {{
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
}}
|
|
.artist-button {{
|
|
padding: 6px 12px;
|
|
border: 1px solid #ccc;
|
|
background-color: white;
|
|
color: #666;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 0.9em;
|
|
transition: all 0.3s;
|
|
}}
|
|
.artist-button:hover {{
|
|
background-color: #f0f0f0;
|
|
border-color: #2196F3;
|
|
}}
|
|
.artist-button.active {{
|
|
background-color: #2196F3;
|
|
color: white;
|
|
border-color: #2196F3;
|
|
}}
|
|
.album[data-artist] {{
|
|
display: block;
|
|
}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>🎵 MusicBrainz Albums - Add & Update</h1>
|
|
<div class="summary">
|
|
<strong>Albums to ADD: {add_count}</strong> | <strong>Albums to UPDATE: {update_count}</strong>
|
|
</div>
|
|
<div class="filter-container">
|
|
<div class="filter-group">
|
|
<label>Filter by Type:</label>
|
|
<div class="filter-buttons">
|
|
<button class="filter-button active" data-filter="all">Show All</button>
|
|
<button class="filter-button" data-filter="add">To ADD ({add_count})</button>
|
|
<button class="filter-button" data-filter="update">To UPDATE ({update_count})</button>
|
|
</div>
|
|
</div>
|
|
<div class="filter-group">
|
|
<label>Filter by Artist:</label>
|
|
<div class="artist-buttons">
|
|
<button class="artist-button active" data-artist="all">All Artists</button>
|
|
{artist_buttons}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {{
|
|
const typeButtons = document.querySelectorAll('.filter-button');
|
|
const artistButtons = document.querySelectorAll('.artist-button');
|
|
const addSection = document.getElementById('albums-to-add');
|
|
const updateSection = document.getElementById('albums-to-update');
|
|
|
|
let currentTypeFilter = 'all';
|
|
let currentArtistFilter = 'all';
|
|
|
|
function applyFilters() {{
|
|
const albums = document.querySelectorAll('.album');
|
|
|
|
albums.forEach(album => {{
|
|
const albumArtist = album.getAttribute('data-artist');
|
|
const isInAddSection = addSection && addSection.contains(album);
|
|
const isInUpdateSection = updateSection && updateSection.contains(album);
|
|
|
|
let showByType = false;
|
|
if (currentTypeFilter === 'all') {{
|
|
showByType = true;
|
|
}} else if (currentTypeFilter === 'add' && isInAddSection) {{
|
|
showByType = true;
|
|
}} else if (currentTypeFilter === 'update' && isInUpdateSection) {{
|
|
showByType = true;
|
|
}}
|
|
|
|
const showByArtist = currentArtistFilter === 'all' || albumArtist === currentArtistFilter;
|
|
|
|
if (showByType && showByArtist) {{
|
|
album.style.display = 'block';
|
|
}} else {{
|
|
album.style.display = 'none';
|
|
}}
|
|
}});
|
|
|
|
if (addSection) {{
|
|
const hasVisibleAlbums = Array.from(addSection.querySelectorAll('.album'))
|
|
.some(album => album.style.display !== 'none');
|
|
addSection.style.display = hasVisibleAlbums ? 'block' : 'none';
|
|
}}
|
|
|
|
if (updateSection) {{
|
|
const hasVisibleAlbums = Array.from(updateSection.querySelectorAll('.album'))
|
|
.some(album => album.style.display !== 'none');
|
|
updateSection.style.display = hasVisibleAlbums ? 'block' : 'none';
|
|
}}
|
|
}}
|
|
|
|
typeButtons.forEach(button => {{
|
|
button.addEventListener('click', function() {{
|
|
typeButtons.forEach(btn => btn.classList.remove('active'));
|
|
this.classList.add('active');
|
|
currentTypeFilter = this.getAttribute('data-filter');
|
|
applyFilters();
|
|
}});
|
|
}});
|
|
|
|
artistButtons.forEach(button => {{
|
|
button.addEventListener('click', function() {{
|
|
artistButtons.forEach(btn => btn.classList.remove('active'));
|
|
this.classList.add('active');
|
|
currentArtistFilter = this.getAttribute('data-artist');
|
|
applyFilters();
|
|
}});
|
|
}});
|
|
}});
|
|
</script>
|
|
"""
|
|
|
|
album_html = """
|
|
<div class="album" data-artist="{artist_escaped}">
|
|
<div class="album-title">{title}</div>
|
|
<div class="artist-name">by {artist}</div>
|
|
{mb_info}
|
|
{issues_info}
|
|
<div class="links">
|
|
<a href="{atisket_link}" target="_blank" class="link-button atisket">Submit via a-tisket</a>
|
|
<a href="{harmony_link}" target="_blank" class="link-button harmony">Submit via Harmony</a>
|
|
</div>
|
|
<div class="deezer-link">
|
|
<a href="{deezer_url}" target="_blank">View on Deezer</a>
|
|
</div>
|
|
</div>
|
|
"""
|
|
|
|
def format_album(album: Dict, is_update: bool = False) -> str:
|
|
submission_links = album.get("submission_links", {})
|
|
mb_info = ""
|
|
issues_info = ""
|
|
|
|
if is_update:
|
|
mb_url = album.get("mb_url", "")
|
|
if mb_url:
|
|
mb_info = f'<div class="mb-link"><a href="{mb_url}" target="_blank">View on MusicBrainz</a></div>'
|
|
issues = album.get("album_issues", [])
|
|
if issues:
|
|
issues_info = f'<div class="issues">Issues: {", ".join(issues)}</div>'
|
|
|
|
title = album.get("title", "Unknown Title")
|
|
artist = album.get("artist_name", "Unknown Artist")
|
|
artist_escaped = escape(artist)
|
|
atisket_link = submission_links.get("atisket_link", "#")
|
|
harmony_link = submission_links.get("harmony_link", "#")
|
|
deezer_url = submission_links.get("deezer_url", "#")
|
|
|
|
return album_html.format(
|
|
title=escape(title),
|
|
artist=artist,
|
|
artist_escaped=artist_escaped,
|
|
mb_info=mb_info,
|
|
issues_info=issues_info,
|
|
atisket_link=atisket_link,
|
|
harmony_link=harmony_link,
|
|
deezer_url=deezer_url,
|
|
)
|
|
|
|
all_albums = albums_to_add + albums_to_update
|
|
unique_artists = sorted(
|
|
set(album.get("artist_name", "Unknown") for album in all_albums)
|
|
)
|
|
|
|
artist_buttons_html = "".join(
|
|
f'<button class="artist-button" data-artist="{escape(artist)}">{escape(artist)}</button>'
|
|
for artist in unique_artists
|
|
)
|
|
|
|
albums_html = ""
|
|
if albums_to_add:
|
|
albums_html += '<div id="albums-to-add" class="album-section visible">'
|
|
albums_html += "<h2>📥 Albums to ADD (Not in MusicBrainz)</h2>"
|
|
formatted_add = map(lambda album: format_album(album, False), albums_to_add)
|
|
albums_html += "".join(formatted_add)
|
|
albums_html += "</div>"
|
|
|
|
if albums_to_update:
|
|
albums_html += '<div id="albums-to-update" class="album-section visible">'
|
|
albums_html += "<h2>🔄 Albums to UPDATE (Need Linking/Updates)</h2>"
|
|
formatted_update = map(
|
|
lambda album: format_album(album, True), albums_to_update
|
|
)
|
|
albums_html += "".join(formatted_update)
|
|
albums_html += "</div>"
|
|
|
|
add_count = len(albums_to_add)
|
|
update_count = len(albums_to_update)
|
|
html_header = html_content.format(
|
|
add_count=add_count,
|
|
update_count=update_count,
|
|
artist_buttons=artist_buttons_html,
|
|
)
|
|
html_footer = "\n</body>\n</html>\n"
|
|
html_content = html_header + albums_html + html_footer
|
|
|
|
with open("missing_albums.html", "w", encoding="utf-8") as f:
|
|
f.write(html_content)
|
|
print(f"📄 HTML report saved to missing_albums.html")
|