--extract --fromtext Schalter hinzu, um die regulären Ausdrücke in der config testen zu können. Konfigurationsdatei erweitert, so dass CAD Begriffe, Maßeinheiten etc. nicht in die Übersetzungstexte kommen

This commit is contained in:
2025-12-03 19:05:31 +01:00
parent ed416a5315
commit 56979938f5
3 changed files with 426 additions and 69 deletions
+176
View File
@@ -0,0 +1,176 @@
[trigramme]
from Storage level=z úložné úrovně
rail bottom edge=spodní hrana kolejnice
Loaded Minitrolleys to=Naložené minivozíky do
to Storage level=do úložné úrovně
to operational area=do provozní oblasti
Empty Minitrolleys to=Prázdné minivozíky do
Minitrolleys to storage=Minivozíky do úložiště
Multi Purpose Station=Víceúčelová stanice
Rail Bottom Edge=Spodní hrana kolejnice
hangsacks max capacity=maximální kapacita závěsných pytlů
Empty Minitrolleys Buffer=Prázdné minivozíky Buffer
Minitrolleys Buffer Loading=Minivozíky Pufr Nakládání
Minitrolleys to Inbound=Minivozíky do příjmu
loaded Minitrolleys to=naložené minivozíky do
Minitrolleys to Storage=Minivozíky do skladu
to Inbound empty=do příjmu prázdné
empty Minitrolleys from=prázdné minivozíky z
Length of Trolley=Délka vozíku
[bigramme]
AMR area=AMR area
AMR ILS=AMR ILS
AMR loaded=AMR loaded
Belastung Abhänger=Zatížení závěsu
Belastung Hänger=Zatížení závěsu
Belastung Säule=Zatížení sloupu
Bottom Edge=Bottom Edge
bottom edge=spodní hrana
Buffer Loading=Buffer Loading
capacity x=kapacita x
compressed air=stlačený vzduch
Control cabinet=Rozvaděč
Dimensions L=Rozměry L
Distance to=Vzdálenost k
distributor omniflo=distributoru omniflo
Empty Minitrolleys=Prázdné minivozíky
empty Minitrolleys=prázdné minivozíky
Fire door=Požární dveře
from AMR=z AMR
from Storage=ze skladu
GOH Storage=Sklad GOH
GOH storage=Sklad GOH
hangsack =závěsný vak
hangsacks max=závěsné vaky max.
Inbound empty=Příchozí prázdné
label Printer=tiskárna štítků
Lenght Measurement=Měření délky
Length of=Délka
line x=řady x
lines GOH=řady GOH
lines x=řady x
loaded Minitrolleys=naložené minivozíky
Loaded Minitrolleys=Naložené minivozíky
Loading Boom=Nakládací rameno
Loading Buff=Nakládací nárazník
long x=dlouhý x
max capacity=max. kapacita
Minitrolleys Buffer=Minivozíky Buffer
Minitrolleys from=Minivozíky z
Minitrolleys to=Minitrolleys to
MT line=MT line
Multi Purpose=Multi Purpose
of Trolley=of Trolley
Operation panel=Operation panel
operational area=operational area
Purpose Station=Purpose Station
rail bottom=rail bottom
Rail Bottom=Rail Bottom
Säule=Säule
Schnitt A=Schnitt A
Schnitt Highway=Schnitt Highway
short x=short x
storage level=storage level
Storage level=Storage level
Storage Level=Storage Level
Sub distributor=Sub distributor
to AMR=to AMR
to Inbound=to Inbound
to L=to L
to operational=to operational
to Q=to Q
to storage=to storage
to Storage=to Storage
Trolley MT=Trolley MT
with TEF=with TEF
[single]
# deutsche Begriffe und ihre tschechische Übersetzung
# Format: Begriffe in der dxf=tschechisch
# ## deutsch
Abhänger=Závěs
Belastung=Zatížení
Säule=Sloup
Abhänger=Závěs
Schnitt=Řez
Unterverteiler=Rozvaděč
Schaltschrank=Rozvaděč
zur=k
ca=ca
Hänger=Závěs
# ## english
air=prázdná
area=oblast
Boom=Rameno
bottom=Kolejnice
Bottom=Spodní
buffer=paleta
Buffer=Rozměry
cabinet=Ovládací
capacity=kapacitu
compressed=hrana
control=ovládání
Control=Sběrné místo
Dimensions=skříň
Distance=naložené
distributor=distributor
door=dveře
Door=Dveře
edge=kolejnice
Edge=Okraj
empty=krátká
Empty=Prázdná
Exit=Výstup
Fire=Požární
Firewall=Požární
for=pro
from=z
hangsack=Stanice
hangsacks=vzduch
Highway=Dálniční
Inbound=stlačená
label=etiketa
Lenght=Délka
Length=Délka
level=úroveň
Level=Úroveň
line=linka
lines=linky
Loaded=dlouhý
loaded=závěsné vaky
Loading=Vzdálenost
long=Buffer
max=max.
Measurement=Měření
Multi=buffer
of=odchozího
Operation=Provoz
operational=Nakládání
Outbound=Odchozí
pallet=závěsný vak
panel=panel
Pickstation=Příchozí
Plastic=Plastová
Printer=Tiskárna
Purpose=Víceúčelový
Rail=provozní
rail=spodní
short=stěna
Station=Naložený
Storage=Skladování
storage=skladovací
Sub=Pod
System=systém
to=do
Trolley=Vozík
Unterverteiler=Podrozdělovač
Schaltschrank=Rozvaděč
waste=odpad
Weight=Hmotnost
with=s
+33 -8
View File
@@ -6,13 +6,13 @@
# Häufige technische Abkürzungen und Codes # Häufige technische Abkürzungen und Codes
layer = LAYER=* layer = LAYER=*
dim = DIM*
special = %%* special = %%*
# Koordinaten und technische Angaben # Koordinaten und technische Angaben
coord_x = X=* coord_x = X=*
coord_y = Y=* coord_y = Y=*
coord_z = Z=* coord_z = Z=*
KR_code = KR-M*
@@ -21,12 +21,30 @@ coord_z = Z=*
# Ein Ausdruck pro Zeile (Python regex Syntax) # Ein Ausdruck pro Zeile (Python regex Syntax)
# Format: pattern_name = regex # Format: pattern_name = regex
# Dezimalzahl mit Punkt oder Komma
decimal_number = ^-?\d+([.,]\d+)?$
pos_decimal_number = ^\+?\d+([.,]\d+)?$
# Prozentangaben
percent = ^-?\d+([.,]\d+)?\s*%$
# Angaben wie =A1 +AB12 oder =A2+UZ304 # Angaben wie =A1 +AB12 oder =A2+UZ304
anlagen_nr = ^=A\d\s*\+[A-Z]{2}\d+ anlagen_nr = ^=A\d\s*\+[A-Z]{2}\d+
anlagen_nr3 = ^=A\d\+UZ\d{3} anlagen_nr3 = ^=A\d\+UZ\d{3}
# Zeichen von A bis F als einzelnes Zeichen
ein_zeichen = ^[A-F]$ ein_zeichen = ^[A-F]$
# A.1 bold
single_letters = ^\\f.*;[A-Z]\.\d$
AX = ^[A-F]\d$
ILSCV = ^ILS-CV\\PM\d{4}$
# # Blöcke wie BLOCK 1.1.2
# BLOCK_3 = ^BLOCK \d\.\d(\.\d)?$
# BLOCK_2 = ^BLOCK \d\.\d$
# BLOCK_1 = ^BLOCK \d\$
# Technische Angaben # Technische Angaben
h1875 = ^\\H1.875x h1875 = ^\\H1.875x
h1875ff = ^\\H1.875x;.*$ h1875ff = ^\\H1.875x;.*$
@@ -37,12 +55,19 @@ m_numbers = ^M\d{3}$
coord_arrows = ^\+\d{1,3}(?:[.,]\d{3})\s*(?:<<<|>>>)\s*\+\d{1,3}(?:[.,]\d{3})$ coord_arrows = ^\+\d{1,3}(?:[.,]\d{3})\s*(?:<<<|>>>)\s*\+\d{1,3}(?:[.,]\d{3})$
# +UC3206\P3 x ET200 SP DI16 402.0 # +UC3206\P3 x ET200 SP DI16 402.0
uc4 = ^\+UC\d{4} uc4 = ^\+UC\d{4}
uc2 = ^=A\d\+UC\d{2} ax_uc4 = ^=A\d\+UC\d{2,4}
uc3 = ^=A\d\+UC\d{3} ax_uz4 = ^=A\d\+UZ\d{2,4}
uz2 = ^=A\d\+UZ\d{2} a0x_uz1= ^=A01\+UZ\d+
uz3 = ^=A\d\+UZ\d{3} a0x_uc4 = ^=A0\d\+UC\d{2,4}
a0x_uz4 = ^=A\d\+UZ\d{2,4}
pxqc_patterns = ^\\pxqc; =A0[1-9]\+UC\d{2,4}$
pxqc_patterns2 = ^\\pxqc; =A1\+UC\d{2,4}$
pxqc_patterns3 = ^\\pxqc; =A1\+UH\d{2,4}$
ip_adresse = ^\\fArial Narrow.*(\d{1,3}\.){3}\d{1,3}$
ip_adress = ^.*;(\d{1,3}\.){3}\d{1,3}$
# TUNNEL Bezeichnungen wie \fArial TUNNEL12-3456
tunnel = ^\\fArial.*TUNNEL\d+-\d{4}$ tunnel = ^\\fArial.*TUNNEL\d+-\d{4}$
kr = ^KR-\d{3,4}$ kw_A = ^\d+\s*KW\s*-\s*\d+\s*A$
+217 -61
View File
@@ -13,8 +13,10 @@ from openpyxl import Workbook
""" """
Dieses Programm: Dieses Programm:
- liest eine DXF Datei und sammelt alle TEXT und MTEXT Objekte - liest eine DXF Datei und sammelt alle TEXT und MTEXT Objekte
- schreibt diese in die erste Spalte einer Excel-Datei ODER liest Texte aus einer TXT Datei (ein Text pro Zeile)
- speichert die Excel-Datei im PROJECT_WORK Verzeichnis - filtert die Texte basierend auf Mustern in translator.cfg
- schreibt die Ergebnisse in eine Excel- oder Textdatei
- speichert die Ausgabedatei im PROJECT_WORK Verzeichnis
""" """
@@ -117,23 +119,21 @@ def should_ignore_text(text: str, wildcard_patterns: list[str], regex_patterns:
return False return False
def extract_text_from_dxf(filename: Path, wildcard_patterns: list[str], regex_patterns: list[str], debug: bool = False) -> list[str]: def extract_text_from_dxf(filename: Path) -> list[str]:
""" """
Extrahiert alle TEXT und MTEXT Objekte aus einer DXF Datei. Extrahiert alle TEXT und MTEXT Objekte aus einer DXF Datei.
Verwendet ein Set, um Duplikate zu vermeiden. Verwendet ein Set, um Duplikate zu vermeiden.
Filtert leere Texte heraus.
Args: Args:
filename: Pfad zur DXF Datei filename: Pfad zur DXF Datei
wildcard_patterns: Liste der Wildcard-Muster zum Filtern
regex_patterns: Liste der Regex-Muster zum Filtern
debug: Wenn True, gibt Debug-Informationen aus
Returns: Returns:
Alphabetisch sortierte Liste mit allen gefundenen Texten (nach Filterung, ohne Duplikate) Alphabetisch sortierte Liste aller gefundenen Texte (ohne Duplikate, ohne leere Texte)
""" """
texts_set = set() # Set für eindeutige Texte texts_set = set() # Set für eindeutige Texte
ignored_count = 0
total_count = 0 total_count = 0
empty_count = 0
try: try:
doc = ezdxf.readfile(filename) doc = ezdxf.readfile(filename)
@@ -147,13 +147,10 @@ def extract_text_from_dxf(filename: Path, wildcard_patterns: list[str], regex_pa
# Ignoriere leere Texte # Ignoriere leere Texte
if not text: if not text:
ignored_count += 1 empty_count += 1
continue continue
if should_ignore_text(text, wildcard_patterns, regex_patterns, debug): texts_set.add(text)
ignored_count += 1
else:
texts_set.add(text)
# Sammle alle MTEXT Objekte # Sammle alle MTEXT Objekte
for entity in msp.query('MTEXT'): for entity in msp.query('MTEXT'):
@@ -163,21 +160,18 @@ def extract_text_from_dxf(filename: Path, wildcard_patterns: list[str], regex_pa
# Ignoriere leere Texte # Ignoriere leere Texte
if not text: if not text:
ignored_count += 1 empty_count += 1
continue continue
if should_ignore_text(text, wildcard_patterns, regex_patterns, debug): texts_set.add(text)
ignored_count += 1
else:
texts_set.add(text)
# Konvertiere Set zu sortierter Liste # Konvertiere Set zu sortierter Liste
texts = sorted(texts_set) texts = sorted(texts_set)
print(f"Anzahl gefundener TEXT/MTEXT Objekte: {total_count}") print(f"Anzahl gefundener TEXT/MTEXT Objekte: {total_count}")
print(f"Ignorierte Texte (nach Filter): {ignored_count}") print(f"Leere Texte ignoriert: {empty_count}")
print(f"Duplikate entfernt: {total_count - ignored_count - len(texts)}") print(f"Duplikate entfernt: {total_count - empty_count - len(texts)}")
print(f"Eindeutige Texte für Export: {len(texts)}") print(f"Eindeutige Texte extrahiert: {len(texts)}")
except Exception as e: except Exception as e:
print(f"Fehler beim Lesen der DXF Datei: {e}") print(f"Fehler beim Lesen der DXF Datei: {e}")
@@ -186,6 +180,71 @@ def extract_text_from_dxf(filename: Path, wildcard_patterns: list[str], regex_pa
return texts return texts
def filter_texts(texts: list[str], wildcard_patterns: list[str], regex_patterns: list[str], debug: bool = False) -> tuple[list[str], list[str]]:
"""
Filtert eine Liste von Texten basierend auf Wildcard- und Regex-Mustern.
Diese Funktion ist unabhängig von der Quelle der Texte (DXF oder TXT).
Args:
texts: Liste der zu filternden Texte
wildcard_patterns: Liste der Wildcard-Muster zum Filtern
regex_patterns: Liste der Regex-Muster zum Filtern
debug: Wenn True, gibt Debug-Informationen aus
Returns:
Tuple mit (akzeptierte Texte, ignorierte Texte) - beide alphabetisch sortiert
"""
accepted_texts = []
ignored_texts = []
for text in texts:
if should_ignore_text(text, wildcard_patterns, regex_patterns, debug):
ignored_texts.append(text)
else:
accepted_texts.append(text)
# Sortiere beide Listen alphabetisch
accepted_texts.sort()
ignored_texts.sort()
print(f"\nFilterung abgeschlossen:")
print(f" Akzeptierte Texte: {len(accepted_texts)}")
print(f" Ignorierte Texte: {len(ignored_texts)}")
return accepted_texts, ignored_texts
def read_texts_from_file(filename: Path) -> list[str]:
"""
Liest Texte aus einer UTF-8 Textdatei (ein Text pro Zeile).
Entfernt Duplikate und leere Zeilen.
Args:
filename: Pfad zur Textdatei
Returns:
Alphabetisch sortierte Liste aller Texte (ohne Duplikate, ohne leere Zeilen)
"""
texts_set = set()
try:
with open(filename, 'r', encoding='utf-8') as f:
for line in f:
text = line.strip()
if text: # Ignoriere leere Zeilen
# Konvertiere escaped Zeilenumbrüche zurück
text = text.replace('\\n', '\n').replace('\\r', '\r')
texts_set.add(text)
texts = sorted(texts_set)
print(f"Texte aus Datei gelesen: {len(texts)}")
return texts
except Exception as e:
print(f"Fehler beim Lesen der Textdatei: {e}")
sys.exit(1)
def write_texts_to_excel(texts: list[str], output_path: Path): def write_texts_to_excel(texts: list[str], output_path: Path):
""" """
Schreibt die Texte in die erste Spalte einer Excel-Datei. Schreibt die Texte in die erste Spalte einer Excel-Datei.
@@ -216,28 +275,72 @@ def write_texts_to_excel(texts: list[str], output_path: Path):
sys.exit(1) sys.exit(1)
def write_texts_to_textfile(texts: list[str], ignored_texts: list[str], output_path: Path):
"""
Schreibt die Texte in eine UTF-8 Textdatei.
Jeder Text wird in einer eigenen Zeile geschrieben (auch Multiline-Texte).
Die Datei enthält zwei Teile: akzeptierte und ignorierte Texte.
Args:
texts: Liste der akzeptierten Texte
ignored_texts: Liste der ignorierten Texte
output_path: Pfad zur Ausgabe-Textdatei
"""
try:
with open(output_path, 'w', encoding='utf-8') as f:
# Schreibe akzeptierte Texte
for text in texts:
# Multiline-Texte werden in einer Zeile geschrieben (Zeilenumbrüche ersetzen)
text_single_line = text.replace('\n', '\\n').replace('\r', '\\r')
f.write(text_single_line + '\n')
# Trennzeile
f.write('\n-- durch translator.cfg ausgeschlossen --\n\n')
# Schreibe ignorierte Texte
for text in ignored_texts:
# Multiline-Texte werden in einer Zeile geschrieben (Zeilenumbrüche ersetzen)
text_single_line = text.replace('\n', '\\n').replace('\r', '\\r')
f.write(text_single_line + '\n')
print(f"Textdatei erfolgreich erstellt: {output_path}")
print(f" Akzeptierte Texte: {len(texts)}")
print(f" Ignorierte Texte: {len(ignored_texts)}")
except Exception as e:
print(f"Fehler beim Schreiben der Textdatei: {e}")
sys.exit(1)
if __name__ == '__main__': if __name__ == '__main__':
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Extrahiert TEXT und MTEXT Objekte aus einer DXF Datei und schreibt sie in eine Excel-Datei', description='Extrahiert TEXT und MTEXT Objekte aus einer DXF Datei oder filtert Texte aus einer TXT Datei',
prog='translate' prog='translate'
) )
# Mutually exclusive group: entweder DXF-Extraktion oder TXT-Filterung
input_group = parser.add_mutually_exclusive_group(required=True)
input_group.add_argument(
'-e', '--extract',
action='store_true',
help='Extrahiert Texte aus DXF/DWG Datei (benötigt --filename)'
)
input_group.add_argument(
'--fromtxt',
action='store',
metavar='FILE.txt',
help='Filtert Texte aus einer TXT Datei (ein Text pro Zeile)'
)
parser.add_argument( parser.add_argument(
'-f', '--filename', '-f', '--filename',
action='store', action='store',
required=True, help='DXF/DWG Datei (.dxf, .dwg) die verarbeitet werden soll (nur mit --extract)',
help='DXF Datei die verarbeitet werden soll',
metavar='myfile.dxf' metavar='myfile.dxf'
) )
parser.add_argument(
'-e', '--extract',
action='store_true',
required=True,
help='Hole alle Texte aus der .dxf in eine Excel Datei'
)
parser.add_argument( parser.add_argument(
'-o', '--outname', '-o', '--outname',
action='store', action='store',
help='Name der Ausgabedatei (ohne Pfad, mit oder ohne .xlsx Endung)', help='Name der Ausgabedatei (ohne Pfad, mit oder ohne .xlsx/.txt Endung)',
metavar='output.xlsx' metavar='output.xlsx'
) )
parser.add_argument( parser.add_argument(
@@ -245,9 +348,24 @@ if __name__ == '__main__':
action='store_true', action='store_true',
help='Aktiviert Debug-Ausgabe für Pattern-Matching' help='Aktiviert Debug-Ausgabe für Pattern-Matching'
) )
parser.add_argument(
'-t', '--export-type',
action='store',
default='excel',
choices=['excel', 'text'],
help='Exportformat: excel (Standard) oder text (UTF-8 Textdatei)'
)
args = parser.parse_args() args = parser.parse_args()
# Validierung: --extract benötigt --filename
if args.extract and not args.filename:
parser.error("--extract benötigt --filename")
# Validierung: --filename nur mit --extract
if args.filename and not args.extract:
parser.error("--filename kann nur mit --extract verwendet werden")
# Prüfe Umgebungsvariablen # Prüfe Umgebungsvariablen
work_dir = check_environment_var('PROJECT_WORK') work_dir = check_environment_var('PROJECT_WORK')
cfg_dir = check_environment_var('PROJECT_CFG') cfg_dir = check_environment_var('PROJECT_CFG')
@@ -255,44 +373,82 @@ if __name__ == '__main__':
# Lade Ignore-Muster aus Konfigurationsdatei # Lade Ignore-Muster aus Konfigurationsdatei
wildcard_patterns, regex_patterns = load_ignore_patterns(cfg_dir) wildcard_patterns, regex_patterns = load_ignore_patterns(cfg_dir)
# Prüfe ob Datei eine DXF Datei ist if args.debug:
input_filename = args.filename print("DEBUG-Modus aktiviert")
if not input_filename.lower().endswith(('.dxf', '.dwg')):
print("Nur verfügbar für .dxf oder .dwg Dateien")
sys.exit(1)
# Versuche zuerst Datei im Work-Ordner zu finden # Bestimme Input-Modus und Datei
filename = work_dir / input_filename if args.extract:
if not filename.exists(): # DXF/DWG-Modus
# Falls nicht gefunden, prüfe ob es ein vollständiger Pfad ist input_filename = args.filename
filename = Path(input_filename) file_extension = input_filename.lower()
if not filename.exists():
print(f"Datei nicht gefunden: {input_filename}") if not file_extension.endswith(('.dxf', '.dwg')):
print(f" Geprüft in Work-Ordner: {work_dir / input_filename}") print("Mit --extract nur .dxf oder .dwg Dateien erlaubt")
print(f" Geprüft als vollständiger Pfad: {filename}")
sys.exit(1) sys.exit(1)
# Extrahiere Texte aus DXF # Versuche zuerst Datei im Work-Ordner zu finden
if args.extract: filename = work_dir / input_filename
print(f"Lese DXF Datei: {filename}") if not filename.exists():
if args.debug: # Falls nicht gefunden, prüfe ob es ein vollständiger Pfad ist
print("DEBUG-Modus aktiviert") filename = Path(input_filename)
texts = extract_text_from_dxf(filename, wildcard_patterns, regex_patterns, args.debug) if not filename.exists():
print(f"Datei nicht gefunden: {input_filename}")
print(f" Geprüft in Work-Ordner: {work_dir / input_filename}")
print(f" Geprüft als vollständiger Pfad: {filename}")
sys.exit(1)
# Erstelle Output-Dateinamen print(f"Lese DXF Datei: {filename}")
if args.outname: all_texts = extract_text_from_dxf(filename)
# Verwende benutzerdefinierten Namen
output_filename = args.outname else: # args.fromtxt
# Füge .xlsx Endung hinzu falls nicht vorhanden # TXT-Modus
input_filename = args.fromtxt
if not input_filename.lower().endswith('.txt'):
print("Mit --fromtxt nur .txt Dateien erlaubt")
sys.exit(1)
# Versuche zuerst Datei im Work-Ordner zu finden
filename = work_dir / input_filename
if not filename.exists():
# Falls nicht gefunden, prüfe ob es ein vollständiger Pfad ist
filename = Path(input_filename)
if not filename.exists():
print(f"Datei nicht gefunden: {input_filename}")
print(f" Geprüft in Work-Ordner: {work_dir / input_filename}")
print(f" Geprüft als vollständiger Pfad: {filename}")
sys.exit(1)
print(f"Lese Textdatei: {filename}")
all_texts = read_texts_from_file(filename)
# Schritt 2: Texte filtern
texts, ignored_texts = filter_texts(all_texts, wildcard_patterns, regex_patterns, args.debug)
# Erstelle Output-Dateinamen basierend auf Export-Typ
if args.outname:
# Verwende benutzerdefinierten Namen
output_filename = args.outname
# Füge Endung hinzu falls nicht vorhanden
if args.export_type == 'excel':
if not output_filename.endswith('.xlsx'): if not output_filename.endswith('.xlsx'):
output_filename += '.xlsx' output_filename += '.xlsx'
else: else: # text
# Standardname basierend auf Eingabedatei if not output_filename.endswith('.txt'):
output_filename += '.txt'
else:
# Standardname basierend auf Eingabedatei
if args.export_type == 'excel':
output_filename = filename.stem + "_texts.xlsx" output_filename = filename.stem + "_texts.xlsx"
else: # text
output_filename = filename.stem + "_texts.txt"
output_path = work_dir / output_filename output_path = work_dir / output_filename
# Schreibe Texte in Excel # Schreibe Texte in gewähltem Format
if args.export_type == 'excel':
write_texts_to_excel(texts, output_path) write_texts_to_excel(texts, output_path)
else: # text
write_texts_to_textfile(texts, ignored_texts, output_path)
print("Verarbeitung erfolgreich abgeschlossen.") print("Verarbeitung erfolgreich abgeschlossen.")