my-swingmusic-wrapped/websitepart.py
2025-04-22 01:09:36 +03:00

214 lines
8.3 KiB
Python

import sqlite3
from flask import Flask, render_template_string, redirect
from datetime import datetime
import os
import json
import requests
import re
from collections import defaultdict
app = Flask(__name__)
def is_youtube_id(videoid):
return re.match(r'^[a-zA-Z0-9_-]{11}$', videoid) is not None
def get_db_values(url="https://halhadus.rocks/assets/localwrapped/music.db"):
if not os.path.exists("/var/www/swingmusic/.swingmusic/music.db"):
try:
download = requests.get(url)
with open("/var/www/swingmusic/.swingmusic/music.db", "wb") as f:
f.write(download.content)
except:
print("Could not download the database.")
exit()
def extract_filename(filepath):
return os.path.basename(filepath)
def get_scrobbles():
get_db_values()
swing_conn = sqlite3.connect('/var/www/swingmusic/.swingmusic/swingmusic.db')
swing_cursor = swing_conn.cursor()
swing_cursor.execute('SELECT timestamp, duration, extra FROM scrobble ORDER BY timestamp DESC')
scrobbles = swing_cursor.fetchall()
swing_conn.close()
music_conn = sqlite3.connect('/var/www/swingmusic/.swingmusic/music.db')
music_cursor = music_conn.cursor()
music_cursor.execute('SELECT listmusicname, listvideoid, filelocation FROM music')
music_data = {extract_filename(row[2]): (row[0], row[1]) for row in music_cursor.fetchall()}
music_conn.close()
total_durations = defaultdict(int)
enriched_data = []
for ts, duration, extra_json in scrobbles:
try:
extra = json.loads(extra_json.replace("'", '"'))
filepath = extra.get("filepath", "")
filename = extract_filename(filepath)
if filename in music_data:
title, videoid = music_data[filename]
total_durations[(title, videoid)] += duration
enriched_data.append({
'title': title,
'videoid': videoid,
'duration': duration,
'timestamp': ts
})
except json.JSONDecodeError:
continue
unique_tracks = {}
for track in enriched_data:
key = (track['title'], track['videoid'])
if key not in unique_tracks:
unique_tracks[key] = {
'title': track['title'],
'videoid': track['videoid'],
'total_duration': total_durations[key],
'last_played': track['timestamp']
}
return sorted(unique_tracks.values(), key=lambda x: x['total_duration'], reverse=True)
@app.route('/swingmusicwrapped.html')
def swing_history():
tracks = get_scrobbles()
return render_template_string('''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Halhadus' SwingMusic Wrapped</title>
<link rel="icon" type="image/png" href="assets/favicon.png">
<meta name="description" content="Halhadus' SwingMusic Wrapped">
<meta name="author" content="Halhadus">
<meta property="og:title" content="Halhadus' SwingMusic Wrapped">
<meta property="og:description" content="Halhadus' SwingMusic Wrapped">
<meta property="og:url" content="https://halhadus.rocks/swingmusicwrapped.html">
<style>
:root {
--bg-color: #1f1f1f;
--card-bg: #2a2a2a;
--border-color: #333;
--text-color: #ffffff;
--font-family: 'JetBrains Mono', monospace;
}
body {
background: var(--bg-color);
color: var(--text-color);
font-family: var(--font-family);
margin: 0;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header-buttons {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
}
.play-button {
background: var(--card-bg);
border: 1px solid var(--border-color);
color: var(--text-color);
padding: 0.8rem 1.5rem;
border-radius: 5px;
text-decoration: none;
display: inline-flex;
align-items: center;
}
.play-button:hover {
background: #444;
}
.history-table {
width: 100%;
border-collapse: collapse;
margin-top: 2rem;
}
.history-table th, .history-table td {
border: 1px solid var(--border-color);
padding: 12px;
text-align: left;
}
.history-table th {
background: var(--card-bg);
}
.thumbnail {
width: 80px;
border-radius: 4px;
aspect-ratio: 16/9;
object-fit: cover;
border: 2px solid var(--border-color);
}
.no-thumbnail {
width: 80px;
height: 45px;
background: #333;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.no-thumbnail span {
color: #666;
font-size: 0.8rem;
}
</style>
</head>
<body>
<div class="container">
<h1>🎵 Halhadus' SwingMusic Wrapped</h1>
<div class="header-buttons">
<a href="/index.html" class="play-button">← Main Page</a>
<a href="https://git.halhadus.rocks/Halhadus/my-swingmusic-wrapped" class="play-button">📁 Source Code</a>
</div>
<table class="history-table">
<thead>
<tr>
<th>Cover</th>
<th>Song</th>
<th>Total Play Time</th>
<th>Last Play Date</th>
<th>Play</th>
</tr>
</thead>
<tbody>
{% for track in tracks %}
<tr>
<td>
{% if is_youtube(track.videoid) %}
<img src="https://img.youtube.com/vi/{{ track.videoid }}/hqdefault.jpg" class="thumbnail" alt="{{ track.title }} cover">
{% else %}
<div class="no-thumbnail">
<span>N/A</span>
</div>
{% endif %}
</td>
<td>{{ track.title }}</td>
<td>{{ (track.total_duration // 3600)|int }}h {{ ((track.total_duration % 3600) // 60)|int }}m</td>
<td>{{ datetime.fromtimestamp(track.last_played).strftime('%Y-%m-%d %H:%M') }}</td>
<td><a href="https://music.youtube.com/watch?v={{ track.videoid }}" class="play-button" target="_blank">▶ YT Music</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
''', tracks=tracks, datetime=datetime, is_youtube=is_youtube_id)
@app.route('/<path:path>')
def catch_all(path):
return redirect(f'https://halhadus.rocks/{path}')
@app.route('/')
def index():
return redirect('https://halhadus.rocks')
if __name__ == '__main__':
app.run(host='127.0.0.1', port=os.environ.get("PORT"))