Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python3
- from pathlib import Path
- import xml.etree.ElementTree as ET
- from collections import Counter
- import html
- import sys
- import math
- def extract_playcounts(plist_path: Path):
- parser = ET.XMLParser(target=ET.TreeBuilder(insert_comments=True))
- tree = ET.parse(str(plist_path), parser=parser)
- root = tree.getroot()
- top = root.find("dict")
- if top is None:
- raise RuntimeError("Top-level <dict> not found.")
- kids = list(top)
- tracks = None
- for i, e in enumerate(kids):
- if e.tag == "key" and e.text == "Tracks":
- if i+1 < len(kids) and kids[i+1].tag == "dict":
- tracks = kids[i+1]
- break
- if tracks is None:
- raise RuntimeError("Tracks <dict> not found.")
- artist_counts = Counter()
- album_counts = Counter()
- song_counts = Counter()
- tc = list(tracks)
- for i in range(0, len(tc), 2):
- if i+1 >= len(tc): break
- info = tc[i+1]
- if info.tag != "dict": continue
- elems = list(info)
- artist = None
- album = None
- name = None
- playcount = 0
- for j in range(0, len(elems)-1):
- if elems[j].tag == "key":
- k = elems[j].text
- v = elems[j+1]
- if k == "Artist":
- artist = (v.text or "").strip()
- elif k == "Album":
- album = (v.text or "").strip()
- elif k == "Name":
- name = (v.text or "").strip()
- elif k == "Play Count":
- try:
- playcount = int(v.text or "0")
- except ValueError:
- playcount = 0
- if artist:
- artist_counts[artist] += playcount
- if album:
- album_counts[album] += playcount
- if name:
- song_counts[name] += playcount
- return artist_counts, album_counts, song_counts
- def build_css():
- return """
- body {
- background: #99ccff;
- font-family: Arial, Helvetica, sans-serif;
- color: #000;
- margin: 0;
- padding: 0;
- text-align: center;
- }
- .wrap {
- width: 800px;
- margin: 0 auto;
- background: #ffffff;
- border: 3px solid #000080;
- box-shadow: 5px 5px 10px #666;
- padding: 20px;
- }
- h1 {
- font-family: Arial, Helvetica, sans-serif;
- font-size: 28px;
- color: #000080;
- text-shadow: 1px 1px #ccc;
- margin-bottom: 20px;
- }
- h2 {
- font-size: 20px;
- color: #333;
- border-bottom: 2px solid #000080;
- padding-bottom: 5px;
- margin-top: 30px;
- }
- a {
- color: #000080;
- text-decoration: underline;
- }
- input {
- width: 90%;
- padding: 5px;
- margin: 10px 0;
- border: 2px inset #ccc;
- font-size: 14px;
- background: #ffffcc;
- }
- table {
- width: 100%;
- border: 2px solid #000080;
- border-collapse: collapse;
- margin-bottom: 20px;
- }
- th {
- background: #3366cc;
- color: #fff;
- font-size: 14px;
- padding: 8px;
- border: 1px solid #000080;
- }
- td {
- background: #e6f0ff;
- border: 1px solid #000080;
- padding: 6px;
- font-size: 13px;
- }
- tbody tr:hover {
- background: #ffff99;
- }
- .footer {
- font-size: 12px;
- color: #666;
- margin-top: 20px;
- border-top: 1px dashed #666;
- padding-top: 10px;
- }
- """
- def build_table(title, counts, limit=None, table_id=""):
- rows = []
- rank = 1
- items = counts.most_common(limit) if limit else counts.most_common()
- for item, total in items:
- rows.append(f"<tr><td>{rank}</td><td>{html.escape(item)}</td><td>{total}</td></tr>")
- rank += 1
- if not rows:
- rows.append("<tr><td colspan='3'>No data</td></tr>")
- return f"""
- <h2><a href="{title.lower()}_1.html">{title}</a></h2>
- <table id="{table_id}">
- <thead>
- <tr><th style="width:70px">#</th><th>{title[:-1]}</th><th style="width:120px">Plays</th></tr>
- </thead>
- <tbody>
- {''.join(rows)}
- </tbody>
- </table>
- """
- def build_full_page(title, counts, filename, per_page=100):
- css = build_css()
- items = counts.most_common()
- pages = math.ceil(len(items)/per_page)
- pages_html = []
- for p in range(pages):
- start = p*per_page
- end = start+per_page
- chunk = items[start:end]
- rows = []
- rank = start+1
- for item, total in chunk:
- rows.append(f"<tr><td>{rank}</td><td>{html.escape(item)}</td><td>{total}</td></tr>")
- rank += 1
- nav = " ".join(
- f'<a href="{filename.replace(".html","")}_{i+1}.html">[{i+1}]</a>'
- for i in range(pages)
- )
- page_html = f"""<!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <title>{title} - Page {p+1}</title>
- <style>{css}</style>
- </head>
- <body>
- <div class="wrap">
- <h1>{title} (Page {p+1} of {pages})</h1>
- <table>
- <thead>
- <tr><th style="width:70px">#</th><th>{title[:-1]}</th><th style="width:120px">Plays</th></tr>
- </thead>
- <tbody>
- {''.join(rows)}
- </tbody>
- </table>
- <div>{nav}</div>
- <div class="footer"><a href="music_stats.html">Back to main page</a></div>
- </div>
- </body>
- </html>"""
- pages_html.append((f"{filename.replace('.html','')}_{p+1}.html", page_html))
- return pages_html
- def build_main_page(artist_counts, album_counts, song_counts):
- css = build_css()
- return f"""<!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <title>Music Stats (2005 Style)</title>
- <style>{css}</style>
- <script>
- function searchAll() {{
- var input = document.getElementById('searchBox');
- var filter = input.value.toLowerCase();
- var tables = ['artistsTable','albumsTable','songsTable'];
- tables.forEach(function(tbl) {{
- var rows = document.querySelectorAll('#' + tbl + ' tbody tr');
- rows.forEach(function(row, index) {{
- var txt = row.cells[1].textContent || row.cells[1].innerText;
- if (filter) {{
- row.style.display = txt.toLowerCase().indexOf(filter) > -1 ? '' : 'none';
- }} else {{
- row.style.display = index < 20 ? '' : 'none';
- }}
- }});
- }});
- }}
- </script>
- </head>
- <body>
- <div class="wrap">
- <h1>Top Music Stats</h1>
- <div class="meta">Generated from Winamp/iTunes library</div>
- <h2>Search Artists, Albums, or Songs</h2>
- <input type="text" id="searchBox" onkeyup="searchAll()" placeholder="Search for anything...">
- {build_table("Artists", artist_counts, limit=20, table_id="artistsTable")}
- {build_table("Albums", album_counts, limit=20, table_id="albumsTable")}
- {build_table("Songs", song_counts, limit=20, table_id="songsTable")}
- <div class="footer">Created locally • {len(artist_counts)} artists, {len(album_counts)} albums, {len(song_counts)} songs</div>
- </div>
- </body>
- </html>"""
- def main():
- in_path = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("test.txt")
- out_dir = Path(sys.argv[2]) if len(sys.argv) > 2 else Path(".")
- out_dir.mkdir(parents=True, exist_ok=True)
- artist_counts, album_counts, song_counts = extract_playcounts(in_path)
- # Main page
- main_html = build_main_page(artist_counts, album_counts, song_counts)
- (out_dir/"music_stats.html").write_text(main_html, encoding="utf-8")
- # Full pages
- for title, counts, filename in [
- ("Artists", artist_counts, "artists.html"),
- ("Albums", album_counts, "albums.html"),
- ("Songs", song_counts, "songs.html"),
- ]:
- pages = build_full_page(title, counts, filename)
- for fname, content in pages:
- (out_dir/fname).write_text(content, encoding="utf-8")
- print(f"Wrote output to {out_dir} with {len(artist_counts)} artists, {len(album_counts)} albums, {len(song_counts)} songs.")
- if __name__ == "__main__":
- main()
Advertisement
Add Comment
Please, Sign In to add comment