Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b18cd9495 | |||
| d1d1788a3c | |||
| fde69adcec | |||
| 44857c6a3c | |||
| 9173e59e1e | |||
| fa59ef5e8a | |||
| 10674a1454 |
41
README.md
41
README.md
@@ -98,16 +98,51 @@ pip install pdfplumber icalendar pypdf2 pytz
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Schnellstart
|
||||
### Schnellstart (CLI)
|
||||
|
||||
1. Kopieren Sie Ihre Dienstplan-PDF-Dateien in dieses Verzeichnis
|
||||
1. Kopieren Sie Ihre Dienstplan-PDF-Dateien in ein Verzeichnis
|
||||
2. Führen Sie das Skript aus:
|
||||
|
||||
```bash
|
||||
python3 pdf_to_ics.py
|
||||
```
|
||||
|
||||
Das Tool findet automatisch alle `.pdf` Dateien und erstellt entsprechende `.ics` Dateien.
|
||||
Das Tool findet automatisch alle `.pdf` Dateien im aktuellen Verzeichnis und erstellt entsprechende `.ics` Dateien.
|
||||
|
||||
### Kommandozeilen-Optionen
|
||||
|
||||
```bash
|
||||
# Alle PDFs im aktuellen Verzeichnis konvertieren
|
||||
python3 pdf_to_ics.py
|
||||
|
||||
# PDFs aus einem bestimmten Verzeichnis konvertieren
|
||||
python3 pdf_to_ics.py --input ./pdfs
|
||||
|
||||
# PDFs in anderes Verzeichnis speichern
|
||||
python3 pdf_to_ics.py --input ./pdfs --output ./ics_dateien
|
||||
|
||||
# Ruhetage ausschließen
|
||||
python3 pdf_to_ics.py --exclude-rest
|
||||
|
||||
# Einzelne PDF-Datei konvertieren
|
||||
python3 pdf_to_ics.py /pfad/zur/datei.pdf
|
||||
|
||||
# Mit detaillierter Ausgabe
|
||||
python3 pdf_to_ics.py --input ./pdfs -v
|
||||
|
||||
# Hilfe anzeigen
|
||||
python3 pdf_to_ics.py --help
|
||||
```
|
||||
|
||||
**Verfügbare Optionen:**
|
||||
|
||||
| Option | Kurzform | Beschreibung |
|
||||
|--------|----------|-------------|
|
||||
| `--input DIR` | `-i` | Eingabe-Verzeichnis mit PDF-Dateien (Standard: aktuelles Verzeichnis) |
|
||||
| `--output DIR` | `-o` | Ausgabe-Verzeichnis für ICS-Dateien (Standard: Eingabe-Verzeichnis) |
|
||||
| `--exclude-rest` | `-e` | Ruhetage ausschließen (Ruhe, R56, R36, vRWF48, RWE, vR48) |
|
||||
| `--verbose` | `-v` | Detaillierte Ausgabe anzeigen |
|
||||
| `--help` | `-h` | Hilfe anzeigen |
|
||||
|
||||
### Erweiterte Nutzung
|
||||
|
||||
|
||||
242
gui.py
242
gui.py
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
GUI für PDF zu ICS Konverter
|
||||
Grafische Benutzeroberfläche mit Tkinter
|
||||
@@ -10,6 +11,7 @@ from pathlib import Path
|
||||
import threading
|
||||
import re
|
||||
import json
|
||||
import webbrowser
|
||||
from pdf_to_ics import extract_dienstplan_data, create_ics_from_dienstplan
|
||||
from update_checker import check_for_updates, get_current_version
|
||||
|
||||
@@ -37,6 +39,9 @@ class PDFtoICSGUI:
|
||||
# Variablen
|
||||
self.pdf_files = []
|
||||
|
||||
# Erstelle Menüleiste
|
||||
self.create_menu()
|
||||
|
||||
# Nutze letztes Ausgabeverzeichnis oder Standard
|
||||
default_dir = self.config.get('last_output_dir', None)
|
||||
if not default_dir or not Path(default_dir).exists():
|
||||
@@ -246,6 +251,243 @@ class PDFtoICSGUI:
|
||||
print(f"Warnung: Konfiguration konnte nicht geladen werden: {e}")
|
||||
return {}
|
||||
|
||||
def create_menu(self):
|
||||
"""Erstelle die Menüleiste"""
|
||||
menubar = tk.Menu(self.root)
|
||||
self.root.config(menu=menubar)
|
||||
|
||||
# Hilfe-Menü
|
||||
help_menu = tk.Menu(menubar, tearoff=0)
|
||||
menubar.add_cascade(label="Hilfe", menu=help_menu)
|
||||
help_menu.add_command(label="PDF-Export auf Android (iPD)", command=self.show_android_export_guide)
|
||||
help_menu.add_separator()
|
||||
help_menu.add_command(label="Über dieses Programm", command=self.show_about_dialog)
|
||||
help_menu.add_separator()
|
||||
help_menu.add_command(label="Beenden", command=self.on_closing)
|
||||
|
||||
def show_android_export_guide(self):
|
||||
"""Zeige Anleitung für PDF-Export aus Android App (iPD)"""
|
||||
guide_window = tk.Toplevel(self.root)
|
||||
guide_window.title("PDF-Export auf Android (iPD)")
|
||||
guide_window.geometry("550x550")
|
||||
guide_window.resizable(False, False)
|
||||
|
||||
# Zentriere das Fenster
|
||||
guide_window.transient(self.root)
|
||||
guide_window.grab_set()
|
||||
|
||||
# Header
|
||||
header = tk.Label(
|
||||
guide_window,
|
||||
text="PDF-Export aus iPD",
|
||||
font=("Arial", 16, "bold"),
|
||||
bg="#2c3e50",
|
||||
fg="white",
|
||||
pady=15
|
||||
)
|
||||
header.pack(fill=tk.X)
|
||||
|
||||
# Content Frame
|
||||
content = tk.Frame(guide_window, padx=20, pady=20)
|
||||
content.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Anleitung-Text
|
||||
guide_text = tk.Text(
|
||||
content,
|
||||
height=20,
|
||||
font=("Courier", 9),
|
||||
fg="#34495e",
|
||||
wrap=tk.WORD,
|
||||
relief=tk.FLAT,
|
||||
bg="#f8f9fa"
|
||||
)
|
||||
|
||||
guide_content = """1. Öffne die iPD App auf deinem Android-Gerät
|
||||
|
||||
2. Öffne einen Dienstplan
|
||||
|
||||
3. Wähle den gewünschten Monat aus
|
||||
|
||||
4. Tippe auf das PDF-Symbol
|
||||
(rechts oben, links neben dem 3-Punkte-Menü)
|
||||
|
||||
5. Tippe auf "Datei herunterladen"
|
||||
(rechts oben, neben Drucker-Button)
|
||||
|
||||
6. Wähle "Im Arbeitsprofil speichern"
|
||||
|
||||
7. Sende die PDF-Datei als E-Mail-Anhang
|
||||
an deine private E-Mailadresse
|
||||
|
||||
8. Transferiere die PDF-Datei auf deinen Computer
|
||||
|
||||
9. Öffne diese Anwendung und füge die PDF ein
|
||||
|
||||
10. Klicke "ICS Datei erstellen"
|
||||
|
||||
11. Importiere die ICS-Datei in deinen Kalender
|
||||
|
||||
✓ Fertig!"""
|
||||
|
||||
guide_text.insert(tk.END, guide_content)
|
||||
guide_text.config(state=tk.DISABLED)
|
||||
guide_text.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Button-Frame
|
||||
button_frame = tk.Frame(content)
|
||||
button_frame.pack(fill=tk.X, pady=(15, 0))
|
||||
|
||||
# Online-Link Button
|
||||
online_btn = tk.Button(
|
||||
button_frame,
|
||||
text="📖 Detaillierte Anleitung online",
|
||||
command=lambda: webbrowser.open("https://git.file-archive.de/webfarben/pdf_to_ics"),
|
||||
bg="#3498db",
|
||||
fg="white",
|
||||
font=("Arial", 10, "bold"),
|
||||
padx=20,
|
||||
pady=10,
|
||||
cursor="hand2"
|
||||
)
|
||||
online_btn.pack(side=tk.LEFT, padx=(0, 10))
|
||||
|
||||
# Close Button
|
||||
close_btn = tk.Button(
|
||||
button_frame,
|
||||
text="Schließen",
|
||||
command=guide_window.destroy,
|
||||
bg="#95a5a6",
|
||||
fg="white",
|
||||
font=("Arial", 10, "bold"),
|
||||
padx=20,
|
||||
pady=10,
|
||||
cursor="hand2"
|
||||
)
|
||||
close_btn.pack(side=tk.LEFT)
|
||||
|
||||
def show_about_dialog(self):
|
||||
"""Zeige About-Dialog mit Programminformationen"""
|
||||
about_window = tk.Toplevel(self.root)
|
||||
about_window.title("Über dieses Programm")
|
||||
about_window.geometry("500x400")
|
||||
about_window.resizable(False, False)
|
||||
|
||||
# Zentriere das Fenster
|
||||
about_window.transient(self.root)
|
||||
about_window.grab_set()
|
||||
|
||||
# Header
|
||||
header = tk.Label(
|
||||
about_window,
|
||||
text="PDF zu ICS Konverter",
|
||||
font=("Arial", 16, "bold"),
|
||||
bg="#2c3e50",
|
||||
fg="white",
|
||||
pady=15
|
||||
)
|
||||
header.pack(fill=tk.X)
|
||||
|
||||
# Content Frame
|
||||
content = tk.Frame(about_window, padx=20, pady=20)
|
||||
content.pack(fill=tk.BOTH, expand=False)
|
||||
|
||||
# Version
|
||||
version = get_current_version()
|
||||
version_label = tk.Label(
|
||||
content,
|
||||
text=f"Version {version}",
|
||||
font=("Arial", 11, "bold"),
|
||||
fg="#2c3e50"
|
||||
)
|
||||
version_label.pack(anchor=tk.W, pady=(0, 15))
|
||||
|
||||
# Info-Texte
|
||||
info_texts = [
|
||||
("Firma:", "Webfarben"),
|
||||
("Programmierer:", "Sebastian Köhler"),
|
||||
("Kontakt:", "kontakt@webfarben.de"),
|
||||
]
|
||||
|
||||
for label, value in info_texts:
|
||||
frame = tk.Frame(content)
|
||||
frame.pack(anchor=tk.W, pady=3, fill=tk.X)
|
||||
|
||||
label_widget = tk.Label(
|
||||
frame,
|
||||
text=label,
|
||||
font=("Arial", 10, "bold"),
|
||||
width=15,
|
||||
anchor=tk.W
|
||||
)
|
||||
label_widget.pack(side=tk.LEFT)
|
||||
|
||||
value_widget = tk.Label(
|
||||
frame,
|
||||
text=value,
|
||||
font=("Arial", 10),
|
||||
fg="#34495e"
|
||||
)
|
||||
value_widget.pack(side=tk.LEFT, padx=(5, 0))
|
||||
|
||||
# Git Repository mit Link
|
||||
repo_frame = tk.Frame(content)
|
||||
repo_frame.pack(anchor=tk.W, pady=(15, 0), fill=tk.X)
|
||||
|
||||
repo_label = tk.Label(
|
||||
repo_frame,
|
||||
text="Repository:",
|
||||
font=("Arial", 10, "bold"),
|
||||
width=15,
|
||||
anchor=tk.W
|
||||
)
|
||||
repo_label.pack(side=tk.LEFT)
|
||||
|
||||
repo_url = "https://git.file-archive.de/webfarben/pdf_to_ics.git"
|
||||
repo_link = tk.Label(
|
||||
repo_frame,
|
||||
text=repo_url,
|
||||
font=("Arial", 10, "underline"),
|
||||
fg="#3498db",
|
||||
cursor="hand2"
|
||||
)
|
||||
repo_link.pack(side=tk.LEFT, padx=(5, 0))
|
||||
repo_link.bind("<Button-1>", lambda e: webbrowser.open(repo_url))
|
||||
|
||||
# Beschreibung
|
||||
desc_frame = tk.Frame(content)
|
||||
desc_frame.pack(anchor=tk.W, pady=(20, 0), fill=tk.BOTH, expand=False)
|
||||
|
||||
desc_text = tk.Text(
|
||||
desc_frame,
|
||||
height=4,
|
||||
font=("Arial", 9),
|
||||
fg="#34495e",
|
||||
wrap=tk.WORD,
|
||||
relief=tk.FLAT,
|
||||
bg=about_window.cget("bg")
|
||||
)
|
||||
desc_text.insert(tk.END,
|
||||
"Ein Programm zur Konvertierung von Dienstplan-PDFs "
|
||||
"zu ICS-Kalenderdateien für einfaches Importieren "
|
||||
"in Kalenderprogramme.")
|
||||
desc_text.config(state=tk.DISABLED)
|
||||
desc_text.pack(fill=tk.BOTH, expand=False)
|
||||
|
||||
# Close Button
|
||||
close_btn = tk.Button(
|
||||
about_window,
|
||||
text="Schließen",
|
||||
command=about_window.destroy,
|
||||
bg="#3498db",
|
||||
fg="white",
|
||||
font=("Arial", 10, "bold"),
|
||||
padx=50,
|
||||
pady=12,
|
||||
cursor="hand2"
|
||||
)
|
||||
close_btn.pack(pady=(10, 15), fill=tk.X, padx=20)
|
||||
|
||||
|
||||
def save_config(self):
|
||||
"""Speichere Konfiguration"""
|
||||
try:
|
||||
|
||||
@@ -208,8 +208,8 @@ print_success "Die Anwendung wurde installiert!"
|
||||
echo ""
|
||||
echo "Sie können die Anwendung nun starten:"
|
||||
echo ""
|
||||
echo " 1. ${BLUE}Über das Anwendungsmenü${NC} (suchen Sie nach 'PDF zu ICS')"
|
||||
echo " 2. ${BLUE}Über die Kommandozeile:${NC} pdf-to-ics"
|
||||
echo -e " 1. ${BLUE}Über das Anwendungsmenü${NC} (suchen Sie nach 'PDF zu ICS')"
|
||||
echo -e " 2. ${BLUE}Über die Kommandozeile:${NC} pdf-to-ics"
|
||||
echo ""
|
||||
echo "Installation Details:"
|
||||
echo " • Installationsverzeichnis: $INSTALL_DIR"
|
||||
|
||||
122
pdf_to_ics.py
122
pdf_to_ics.py
@@ -255,39 +255,131 @@ def create_ics_from_dienstplan(dienstplan, output_path=None, exclude_rest=False)
|
||||
|
||||
def main():
|
||||
"""
|
||||
Hauptfunktion
|
||||
Hauptfunktion mit CLI-Argumenten
|
||||
"""
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
# Finde alle PDF-Dateien im aktuellen Verzeichnis
|
||||
pdf_files = list(Path('.').glob('*.pdf'))
|
||||
parser = argparse.ArgumentParser(
|
||||
description='PDF zu ICS Konverter - Konvertiere Dienstplan-PDFs zu iCalendar-Dateien',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Beispiele:
|
||||
python3 pdf_to_ics.py # Konvertiere alle PDFs im aktuellen Verzeichnis
|
||||
python3 pdf_to_ics.py --input ./pdfs --output ./ics # PDFs aus ./pdfs → ICS zu ./ics
|
||||
python3 pdf_to_ics.py --input ./pdfs --exclude-rest # Schließe Ruhetage aus
|
||||
python3 pdf_to_ics.py file.pdf # Konvertiere einzelne Datei
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'pdf_file',
|
||||
nargs='?',
|
||||
help='Einzelne PDF-Datei zum Konvertieren (optional)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-i', '--input',
|
||||
type=str,
|
||||
default='.',
|
||||
help='Eingabe-Verzeichnis mit PDF-Dateien (Standard: aktuelles Verzeichnis)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
type=str,
|
||||
help='Ausgabe-Verzeichnis für ICS-Dateien (Standard: Eingabe-Verzeichnis)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-e', '--exclude-rest',
|
||||
action='store_true',
|
||||
help='Ruhetage ausschließen (Ruhe, R56, R36, vRWF48, RWE, vR48)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help='Detaillierte Ausgabe'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Bestimme Eingabe-Verzeichnis
|
||||
if args.pdf_file:
|
||||
# Einzelne Datei
|
||||
pdf_file = Path(args.pdf_file)
|
||||
if not pdf_file.exists():
|
||||
print(f"✗ Fehler: Datei nicht gefunden: {pdf_file}")
|
||||
sys.exit(1)
|
||||
pdf_files = [pdf_file]
|
||||
input_dir = pdf_file.parent
|
||||
else:
|
||||
# Verzeichnis
|
||||
input_dir = Path(args.input)
|
||||
if not input_dir.exists():
|
||||
print(f"✗ Fehler: Verzeichnis nicht gefunden: {input_dir}")
|
||||
sys.exit(1)
|
||||
pdf_files = sorted(input_dir.glob('*.pdf'))
|
||||
|
||||
# Bestimme Ausgabe-Verzeichnis
|
||||
output_dir = Path(args.output) if args.output else input_dir
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if not pdf_files:
|
||||
print("Keine PDF-Dateien gefunden!")
|
||||
print(f"⚠ Keine PDF-Dateien gefunden in: {input_dir}")
|
||||
return
|
||||
|
||||
if args.verbose:
|
||||
print(f"📂 Eingabe-Verzeichnis: {input_dir}")
|
||||
print(f"📂 Ausgabe-Verzeichnis: {output_dir}")
|
||||
print(f"📄 PDF-Dateien gefunden: {len(pdf_files)}")
|
||||
if args.exclude_rest:
|
||||
print("🧘 Ruhetage werden ausgeschlossen")
|
||||
print()
|
||||
|
||||
success_count = 0
|
||||
error_count = 0
|
||||
|
||||
for pdf_file in pdf_files:
|
||||
print(f"\nVerarbeite: {pdf_file}")
|
||||
print(f"\n▶ Verarbeite: {pdf_file.name}")
|
||||
|
||||
try:
|
||||
# Extrahiere Daten
|
||||
dienstplan = extract_dienstplan_data(str(pdf_file))
|
||||
|
||||
print(f"Name: {dienstplan['vorname']} {dienstplan['name']}")
|
||||
print(f"Personalnummer: {dienstplan['personalnummer']}")
|
||||
print(f"Betriebshof: {dienstplan['betriebshof']}")
|
||||
print(f"Anzahl der Events: {len(dienstplan['events'])}")
|
||||
if args.verbose:
|
||||
print(f" Name: {dienstplan['vorname']} {dienstplan['name']}")
|
||||
print(f" Personalnummer: {dienstplan['personalnummer']}")
|
||||
print(f" Betriebshof: {dienstplan['betriebshof']}")
|
||||
print(f" Anzahl der Events: {len(dienstplan['events'])}")
|
||||
|
||||
if not dienstplan['events']:
|
||||
print(f" ⚠️ Warnung: Keine Events gefunden!")
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
# Erstelle ICS-Datei
|
||||
ics_path = pdf_file.with_suffix('.ics')
|
||||
create_ics_from_dienstplan(dienstplan, str(ics_path))
|
||||
ics_filename = pdf_file.stem + '.ics'
|
||||
ics_path = output_dir / ics_filename
|
||||
create_ics_from_dienstplan(dienstplan, str(ics_path), exclude_rest=args.exclude_rest)
|
||||
|
||||
print(f"✓ ICS-Datei erstellt: {ics_path}")
|
||||
print(f" ✓ ICS erstellt: {ics_path}")
|
||||
success_count += 1
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Fehler bei {pdf_file}: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
print(f" ✗ Fehler: {str(e)}")
|
||||
if args.verbose:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
error_count += 1
|
||||
|
||||
# Zusammenfassung
|
||||
print("\n" + "="*50)
|
||||
print(f"✅ Fertig!")
|
||||
print(f" Erfolgreich: {success_count}")
|
||||
print(f" Fehler: {error_count}")
|
||||
print("="*50)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.0.0
|
||||
1.1.0
|
||||
|
||||
Reference in New Issue
Block a user