169 lines
5.2 KiB
Python
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()
|