Files
Suisa-Listen/Multi-Musikanteil.py

169 lines
5.2 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Mehrmonats Auswertung ACRCloud PDF Report und Druck
———————————————————————————————————————————————
• Timestamp Spalten : "Timestamp(UTC+0x:00)"
• Dauer Spalte : "Played Duration" (Sekunden)
"""
import calendar
import os
import subprocess
import sys
import tempfile
from datetime import datetime
from pathlib import Path
from typing import List, Tuple
import pandas as pd
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
import tkinter as tk
from tkinter import filedialog, messagebox
possible_cols = [
"Timestamp(UTC+01:00)",
"Timestamp(UTC+02:00)",
]
TS_COL = next(col for col in possible_cols if col in df.columns)
DUR_COL = "Played Duration"
def musikanteil(path: Path) -> Tuple[int, int, float]:
"""liefert (Jahr, Monat, Prozent) für genau eine Datei"""
df = pd.read_excel(path)
if TS_COL not in df.columns or DUR_COL not in df.columns:
raise ValueError(f"{path.name}: Erforderliche Spalten nicht gefunden.")
df[TS_COL] = pd.to_datetime(df[TS_COL], errors="raise")
erstes = df[TS_COL].min()
jahr, monat = erstes.year, erstes.month
gesamt = df[DUR_COL].sum()
sek_monat = calendar.monthrange(jahr, monat)[1] * 86_400
proz = gesamt / sek_monat * 100
return jahr, monat, proz
def erstelle_pdf(daten: List[Tuple[int, int, float]], ausgabe: Path) -> None:
"""Erzeugt einen Querformat PDF Report mit Tabelle."""
c = canvas.Canvas(str(ausgabe), pagesize=landscape(A4))
w, h = landscape(A4)
# Überschrift
title = "Musikanteil pro Monat (ACRCloud Auswertung)"
c.setFont("Helvetica-Bold", 16)
c.drawCentredString(w / 2, h - 25 * mm, title)
# Datum
c.setFont("Helvetica", 9)
c.drawString(15 * mm, h - 32 * mm, f"Erstellt am: {datetime.now():%d.%m.%Y %H:%M}")
# Tabellenkopf
ypos = h - 50 * mm
col_widths = [50 * mm, 50 * mm, 50 * mm]
headers = ["Monat/Jahr", "Musikanteil [%]", "Datei"]
c.setFont("Helvetica-Bold", 11)
for i, head in enumerate(headers):
c.drawString(15 * mm + sum(col_widths[:i]), ypos, head)
# Tabellenzeilen
c.setFont("Helvetica", 11)
ypos -= 8 * mm
for jahr, monat, proz, dateiname in daten:
c.drawString(15 * mm, ypos, f"{monat:02d}/{jahr}")
c.drawRightString(15 * mm + col_widths[0] + col_widths[1] - 5 * mm,
ypos, f"{proz:.2f}")
c.drawString(15 * mm + col_widths[0] + col_widths[1], ypos, dateiname)
ypos -= 7 * mm
if ypos < 20 * mm: # neue Seite
c.showPage()
ypos = h - 25 * mm
c.save()
def drucke_pdf(pfad: Path) -> None:
"""Sendet das PDF an den Windows Standarddrucker (Acrobat bzw. Edge)."""
try:
# os.startfile mit "print" funktioniert auf Windows
os.startfile(pfad, "print")
except Exception as e:
messagebox.showerror("Druckfehler", f"Drucken fehlgeschlagen:\n{e}")
def main() -> None:
root = tk.Tk()
root.withdraw()
dateien = filedialog.askopenfilenames(
title="Mehrere ACRCloud Excel Dateien wählen",
filetypes=[("Excel Dateien", "*.xlsx;*.xls")],
)
if not dateien:
return
ergebnisse = []
fehler = []
for pfad in dateien:
try:
jahr, monat, proz = musikanteil(Path(pfad))
ergebnisse.append((jahr, monat, proz, Path(pfad).name))
except Exception as exc:
fehler.append(f"{Path(pfad).name}: {exc}")
if not ergebnisse:
messagebox.showerror("Fehler", "\n".join(fehler) or "Keine gültigen Dateien.")
return
# chronologisch sortieren
ergebnisse.sort(key=lambda x: (x[0], x[1]))
# PDF Datei speichern
save_path = filedialog.asksaveasfilename(
title="PDF Report speichern unter …",
defaultextension=".pdf",
filetypes=[("PDF Datei", "*.pdf")],
initialfile="Musikanteil_Report.pdf",
)
if not save_path:
return
erstelle_pdf(ergebnisse, Path(save_path))
# Zusammenfassung anzeigen Druckoption
text_lines = [f"{m:02d}/{j}: {p:.2f} %" for j, m, p, _ in ergebnisse]
summary = "Erfolgreich erstellt:\n" + "\n".join(text_lines)
def dialog():
win = tk.Toplevel()
win.title("Auswertung fertig")
win.geometry("320x240")
tk.Label(win, text=summary, justify="left", pady=10).pack()
frame = tk.Frame(win)
frame.pack(pady=10)
tk.Button(frame, text="PDF öffnen",
command=lambda: [os.startfile(save_path), win.destroy()]).pack(side="left", padx=5)
tk.Button(frame, text="Drucken",
command=lambda: [drucke_pdf(Path(save_path)), win.destroy()]).pack(side="left", padx=5)
tk.Button(frame, text="Schließen", command=win.destroy).pack(side="right", padx=5)
win.mainloop()
dialog()
if fehler:
messagebox.showwarning("Einige Dateien übersprungen",
"Folgende Dateien konnten nicht verarbeitet werden:\n" + "\n".join(fehler))
if __name__ == "__main__":
if sys.platform != "win32":
print("Dieses Tool ist für Windows gedacht.")
main()