Jelajahi Sumber

Initialer Commit

Kristian Schultz 11 bulan lalu
melakukan
ca3100d872
5 mengubah file dengan 365 tambahan dan 0 penghapusan
  1. 3 0
      .gitignore
  2. 3 0
      Eingang/README.txt
  3. 10 0
      Karten/README.txt
  4. 202 0
      Programm/adi.py
  5. 147 0
      Programm/qslGen.py

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+Eingang/*
+Karten/*
+Programm/__pycache__

+ 3 - 0
Eingang/README.txt

@@ -0,0 +1,3 @@
+In diesem Ordner bitte die *.adi Dateien abspeichern, die verarbeitet werden sollen.
+
+Achtung! Nach dem Verarbeiten mit qslGen.py werden die *.adi Dateien aus diesem Ordner gelöscht. 

+ 10 - 0
Karten/README.txt

@@ -0,0 +1,10 @@
+In diesem Ordner werden die fertigen Karten gespeichert.
+Die fertigen Katen werden in Ordnern gespeichert.
+
+Angenommen das QSO war mit DG0NKS am 3. April 2025 um 10:25, dann wird die QSL-Karte unter dieser Datei gespeichert:
+
+~/qslKarten/Karten/D/DG/DG0NKS/DG0NKS_03.Apr.2025_11_30.jpg
+
+Falls diese Datei beim generieren bereits existiert, wird eine Nummer an den Dateinamen angehangen. Z.B: 
+
+~/qslKarten/Karten/D/DG/DG0NKS/DG0NKS_03.Apr.2025_11_30-1.jpg

+ 202 - 0
Programm/adi.py

@@ -0,0 +1,202 @@
+
+if __name__ == "__main__":
+  print("This is a library not an executable.")
+  exit(1)
+
+def cleanupXmlName(name):
+  nameOut = ""
+  for c in name:
+    if c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-_.:;/()[]":
+      nameOut += c
+    else:
+      nameOut += f"&#{ord(c)};"
+  return nameOut
+
+
+def cleanupPathName(name):
+  nameOut = ""
+  for c in name:
+    if c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-_.":
+      nameOut += c
+    else:
+      nameOut += "_"
+  return nameOut
+
+def formatTime(text):
+  if ":" in text:
+    return text
+
+  t = ""
+  if len(text) >= 2:
+    t += text[0:2]
+
+    if len(text) >= 4:
+      t += ":" + text[2:4]
+      
+      if len(text) >= 6:
+        t += ":" + text[4:6]
+    else:
+      t += ":00"
+  else:
+    t = "__:__"
+
+  return t
+
+def formatDate(text):
+  if "." in text:
+    return text
+
+  y = text[0:4]
+  m = int(text[4:6])
+  d = text[6:8]
+  
+  month = ["___", "Jan", "Feb", "Mar", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+  if m < 1 or m > 12:
+    m = 0
+
+  return f"{d}.{month[m]}.{y}"
+
+class AdiRow:
+  def __init__(self):
+    self.CALL = ""
+    self.Date = ""
+    self.Time = ""
+    self.Band = ""
+    self.MHz = ""
+    self.Mode = ""
+    self.RST = ""
+
+  def fileName(self):
+    return cleanupPathName(f"{self.CALL}_{self.Date}_{self.Time}")
+
+  def subPath(self):
+    name = cleanupPathName(f"{self.CALL}")
+    if len(name) >= 2:
+      return f"{name[0]}/{name[0:2]}/{name}"
+    if len(name) == 1:
+      return f"{name}/_/{name}"
+    return "_/_/_"
+
+  def fillTemplate(self, template):
+    template = template.replace("{_CALL_}", cleanupXmlName(self.CALL))
+    template = template.replace("{_Date_}", cleanupXmlName(self.Date))
+    template = template.replace("{_Time_}", cleanupXmlName(self.Time))
+    template = template.replace("{_Band_}", cleanupXmlName(self.Band))
+    template = template.replace("{_MHz_}",  cleanupXmlName(self.MHz))
+    template = template.replace("{_Mode_}", cleanupXmlName(self.Mode))
+    template = template.replace("{_RST_}",  cleanupXmlName(self.RST))
+    return template
+
+  def isValid(self):
+    return self.CALL != "" and self.Date != "" and self.Time != ""
+
+  def setValue(self, k, v):
+    k = k.upper()
+    if k == "CALL":
+      self.CALL = v
+    elif k == "QSO_DATE":
+      self.Date = formatDate(v)
+    elif k == "TIME_ON":
+      self.Time = formatTime(v)
+    elif k == "BAND":
+      self.Band = v
+    elif k == "FREQ":
+      self.MHz = v
+    elif k == "MODE":
+      self.Mode = v
+    elif k == "RST_SENT":
+      self.RST = v
+    
+
+  def guessBand(self, freq=None):
+    bands = [ ("160m",  1.8,   2.0)
+            , ( "80m",  3.5,   3.8)
+            , ( "60m",  5.3,   5.4)
+            , ( "40m",  7.0,   7.2)
+            , ( "30m", 10.1,  10.2)
+            , ( "20m", 14.0,  14.5)
+            , ( "17m", 18.0,  18.2)
+            , ( "15m", 21.0,  21.5)
+            , ( "12m", 24.0,  25.0)
+            , ( "10m", 28.0,  29.7)
+            , (  "6m",  50.0,  52.0)
+            , (  "4m",  70.0,  70.3)
+            , (  "2m", 144.0, 146.0)
+            , ("70cm", 430.0, 440.0)
+            ]
+
+    if self.Band != "":
+      return
+
+    if freq is None:
+      freq = self.MHz
+
+    try:
+      freq = float(freq.replace(",","."))
+    except:
+      return
+
+    for (name, lower, upper) in bands:
+      if freq >= lower and freq <= upper:
+        self.Band = name
+        return
+
+
+
+def parseRow(row):
+  state = 0
+  key = ""
+  value = ""
+  adiRow = AdiRow()
+  while row != "":
+    c = row[0]
+    row = row[1:]
+    if state == 0:
+      if c == '<':
+        state = 1
+        key = ""
+    else:
+      if c != ">":
+        key += c
+      else:
+        state = 0
+        pair = key.split(":")
+        if len(pair) != 2:
+          continue
+
+        key = pair[0]
+        try:
+          size = int(pair[1])
+          if size < 1 or size > 100:
+            continue
+          value = row[0:size]
+          row = row[(size+1):]
+          adiRow.setValue(key, value)
+        finally:
+          pass
+          
+  if adiRow.isValid():
+    adiRow.guessBand()
+    return adiRow
+
+  return None
+
+
+def loadAdi(fileName):
+  adiText = ""
+  with open(fileName) as f:
+    adiText = f.read()
+
+  adi = adiText.split("<EOH>")
+  if len(adi) < 2:
+    return None
+
+  adi = "<EOH>".join(adi[1:])
+  data = []
+  for row in adi.split("<EOR>"):
+    row = parseRow(row)
+    if row is not None:
+      data.append(row)
+
+  return data
+  

+ 147 - 0
Programm/qslGen.py

@@ -0,0 +1,147 @@
+#!/usr/bin/python3
+import os
+import sys
+import time
+import adi
+
+baseDir = os.path.expanduser("~/qslKarten")
+inDir = f"{baseDir}/Eingang"
+outDir = f"{baseDir}/Karten"
+templateDir = f"{baseDir}/Vorlage"
+template = None
+dpi = 300
+
+verbose = False
+
+
+
+def exec(cmd, logFile=None):
+  if verbose:
+    print(f"# {cmd}")
+  
+  if logFile is not None:
+    appendFile(logFile, f"# {cmd}")
+    return os.system(f"{cmd} >> {logFile} 2>&1")  
+  return os.system(cmd)
+
+
+def progressBar(nMax, pos, name):
+  p = int(20 * (pos / nMax))
+  msg = "[" + ("=" * p) + (" " * (20 - p)) + "]"
+  msg += f" {pos}/{nMax}: {name}        "
+  print(msg, end="\r")
+  
+
+def appendFile(name, message):
+  with open(name, "at") as f:
+    f.write(f"{message}\n")
+
+
+def createQslCards(fileName, name):
+  global outDir
+  global dpi
+
+  nCards = 0
+
+  logFile = f"{outDir}/logs/{name}.log"
+
+  exec(f"mkdir -p {outDir}/logs")
+  appendFile(logFile, "--------------------")
+  exec(f"date", logFile)
+
+  data = adi.loadAdi(fileName)
+  numOfRows = len(data)
+  for pos, row in enumerate(data):
+    tCall = row.fileName()
+    if not verbose:
+      progressBar(numOfRows, pos + 1, tCall)
+
+    svg = row.fillTemplate(f"{template}")
+
+    d = f"{outDir}/{row.subPath()}"
+    name = f"{d}/{tCall}"
+
+    os.makedirs(d, exist_ok=True)
+
+    n = 0
+    fOut = f'{name}'
+    while os.path.exists(fOut + ".jpg"):
+      n += 1
+      fOut = f'{name}-{n}'
+
+    fTmp = f"{fOut}.svg"
+    with open(fTmp, "wt") as f:
+      f.write(svg)
+
+    if 0 != exec(f"inkscape -C -d {dpi} --export-type=png -o '{fOut}.png' '{fTmp}'", logFile):
+        raise "Problem with inkscape!"
+
+    if 0 != exec(f"convert '{fOut}.png' '{fOut}.jpg'", logFile):
+        raise "Problem with Imagemagick"
+
+    os.remove(f"{fOut}.png")
+    os.remove(fTmp)
+
+    nCards += 1
+
+  print("")
+
+  return nCards
+      
+  
+
+def selectTemplate():
+  templates = [None]
+  for name in os.listdir(templateDir):
+    if name.endswith(".svg"):
+      templates.append(name)
+
+  while True:
+    for k, v in enumerate(templates):
+      if k > 0:
+        print(f"[{k}] '{v}'")
+
+    print()
+    print("[0] Abbrechen")
+    print()
+    x = input("Bitte Nummer waehlen: ")
+    try:
+      x = int(x)
+    except:
+      continue
+
+    if x < 0 or x >= len(templates):
+      continue
+
+    if x == 0 or templates[x] is None:
+      print("* Verarbeitung abgebrochen.")
+      exit(0)
+
+    return templates[x]
+    
+
+
+
+if __name__ == "__main__":
+  if "-v" in sys.argv or "--verbose" in sys.argv:
+    verbose = True
+
+  print("|------------------------------------------------------------")
+  print("| qslGen.py V1.0.1")
+  print("| Programm zum erstellen von QSL-Karten aus ADI Dateien.")
+  print("|------------------------------------------------------------")
+  print("")
+  print("====[ Auswahl der Vorlage ]==================================")
+
+  templateName = selectTemplate()
+  print(f"* Lade Vorlage '{templateName}'")
+  with open(f"{templateDir}/{templateName}", "rt") as f:
+    template = f.read()
+
+  print("====[ Verarbeite ADI Dateien ]===============================")
+  for name in os.listdir(inDir):
+    if name.endswith(".adi"):
+      print(f"* Verarbeite '{name}'")
+      fname = f"{inDir}/{name}"
+      createQslCards(fname, name)
+      os.remove(fname)