From 498f20e623afbdb76904e4a2eb65f40d91315d72 Mon Sep 17 00:00:00 2001 From: Niklas Marion Date: Tue, 30 Jan 2024 13:40:05 +0100 Subject: [PATCH] add fastapi server --- .github/workflows/docker.yaml | 69 +++++++++++++++++++++++ Dockerfile | 10 ++++ README.md | 2 +- bib.py | 31 +++++----- footer/.gitkeep | 0 footer.png => footer/rehlingen.png | Bin header/.gitkeep | 0 {sponsor => header}/bitburger.png | Bin {sponsor => header}/edeka.png | Bin {sponsor => header}/ford.png | Bin {sponsor => header}/globus.png | Bin {sponsor => header}/heitz.png | Bin {sponsor => header}/ikk.png | Bin {sponsor => header}/innenministerium.png | Bin {sponsor => header}/ksk.png | Bin {sponsor => header}/lotto.png | Bin {sponsor => header}/shs.png | Bin {sponsor => header}/vse.png | Bin {sponsor => header}/vvb.png | Bin main.py | 4 +- requirements.txt | 4 +- server.py | 62 ++++++++++++++++++++ 22 files changed, 162 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/docker.yaml create mode 100644 Dockerfile create mode 100644 footer/.gitkeep rename footer.png => footer/rehlingen.png (100%) create mode 100644 header/.gitkeep rename {sponsor => header}/bitburger.png (100%) rename {sponsor => header}/edeka.png (100%) rename {sponsor => header}/ford.png (100%) rename {sponsor => header}/globus.png (100%) rename {sponsor => header}/heitz.png (100%) rename {sponsor => header}/ikk.png (100%) rename {sponsor => header}/innenministerium.png (100%) rename {sponsor => header}/ksk.png (100%) rename {sponsor => header}/lotto.png (100%) rename {sponsor => header}/shs.png (100%) rename {sponsor => header}/vse.png (100%) rename {sponsor => header}/vvb.png (100%) create mode 100644 server.py diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml new file mode 100644 index 0000000..74718af --- /dev/null +++ b/.github/workflows/docker.yaml @@ -0,0 +1,69 @@ +name: Docker + +on: + workflow_dispatch: + push: + branches: + - main +permissions: + actions: write + contents: read + packages: write + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + name: 🐳 Build + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + with: + access_token: ${{ github.token }} + + - name: 🐳 Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: ⚡️ Cache Docker layers + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + - name: 🔑 Github Registry Auth + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: 👀 Extract metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: 🐳 Build + uses: docker/build-push-action@v3 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + COMMIT_SHA=${{ github.sha }} + BRANCH=${{ github.head_ref || github.ref_name }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new + + - name: 🚚 Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bf5523d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.10-alpine + +WORKDIR /code + +COPY . /code + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + + +CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"] \ No newline at end of file diff --git a/README.md b/README.md index bc437d6..a591f5b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ python3 .\main.py --excel .\data.xlsx --output .\output\ --font .\agency_fb.ttf ``` ```bash -python3 .\bib.py --text MARION --output NiklasMarion.png --header .\sponsor\lotto.png --footer .\footer.png --font .\agency_fb.ttf --header-offset 60 +python3 .\bib.py --text MARION --output NiklasMarion.png --header .\header\lotto.png --footer .\footer\rehlingen.png --font .\agency_fb.ttf --header-offset 60 ``` ![image](https://github.com/nimarion/bib-generator/assets/23435250/5a7272fb-e5bb-4117-b3e0-b769daf06912) diff --git a/bib.py b/bib.py index c27a361..1d928bb 100644 --- a/bib.py +++ b/bib.py @@ -1,14 +1,12 @@ from PIL import ImageFont, ImageDraw, Image import argparse -import img2pdf -import os -import uuid +import io -def main(txt, output_file, header_file, footer_file, font_file, header_offset = 0): +def generate_image(txt, header_file, footer_file, font_file, header_offset = 0): # A5 size image = Image.new("RGB", (3508, 2480), "white") - header = Image.open(header_file) - footer = Image.open(footer_file) + header = Image.open("header/" + header_file) + footer = Image.open("footer/" + footer_file) draw = ImageDraw.Draw(image) fontsize = 1 # starting font size @@ -48,16 +46,17 @@ def main(txt, output_file, header_file, footer_file, font_file, header_offset = image.paste(header, (0, header_offset)) image.paste(footer, (0, image.size[1] - footer.size[1])) - if output_file.endswith('.pdf'): - a5 = (img2pdf.mm_to_pt(210), img2pdf.mm_to_pt(148)) - layout = img2pdf.get_layout_fun(a5) - tmp_file = str(uuid.uuid4()) + '.png' - image.save(tmp_file) - with open(output_file, 'wb') as f: - f.write(img2pdf.convert([tmp_file], layout_fun=layout)) - os.remove(tmp_file) - else: - image.save(output_file) + img_byte_arr = io.BytesIO() + image.save(img_byte_arr, format='PNG') + img_byte_arr = img_byte_arr.getvalue() + return img_byte_arr + + + +def main(txt, output_file, header_file, footer_file, font_file, header_offset = 0): + bytes = generate_image(txt, header_file, footer_file, font_file, header_offset) + image = Image.open(io.BytesIO(bytes)) + image.save(output_file) if __name__ == "__main__": parser = argparse.ArgumentParser( diff --git a/footer/.gitkeep b/footer/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/footer.png b/footer/rehlingen.png similarity index 100% rename from footer.png rename to footer/rehlingen.png diff --git a/header/.gitkeep b/header/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/sponsor/bitburger.png b/header/bitburger.png similarity index 100% rename from sponsor/bitburger.png rename to header/bitburger.png diff --git a/sponsor/edeka.png b/header/edeka.png similarity index 100% rename from sponsor/edeka.png rename to header/edeka.png diff --git a/sponsor/ford.png b/header/ford.png similarity index 100% rename from sponsor/ford.png rename to header/ford.png diff --git a/sponsor/globus.png b/header/globus.png similarity index 100% rename from sponsor/globus.png rename to header/globus.png diff --git a/sponsor/heitz.png b/header/heitz.png similarity index 100% rename from sponsor/heitz.png rename to header/heitz.png diff --git a/sponsor/ikk.png b/header/ikk.png similarity index 100% rename from sponsor/ikk.png rename to header/ikk.png diff --git a/sponsor/innenministerium.png b/header/innenministerium.png similarity index 100% rename from sponsor/innenministerium.png rename to header/innenministerium.png diff --git a/sponsor/ksk.png b/header/ksk.png similarity index 100% rename from sponsor/ksk.png rename to header/ksk.png diff --git a/sponsor/lotto.png b/header/lotto.png similarity index 100% rename from sponsor/lotto.png rename to header/lotto.png diff --git a/sponsor/shs.png b/header/shs.png similarity index 100% rename from sponsor/shs.png rename to header/shs.png diff --git a/sponsor/vse.png b/header/vse.png similarity index 100% rename from sponsor/vse.png rename to header/vse.png diff --git a/sponsor/vvb.png b/header/vvb.png similarity index 100% rename from sponsor/vvb.png rename to header/vvb.png diff --git a/main.py b/main.py index 80056f3..dcccfa5 100644 --- a/main.py +++ b/main.py @@ -11,7 +11,7 @@ '--output', '-o', help='Output folder', required=True) argparse.add_argument('--font', '-F', help='Font file name', required=True) argparse.add_argument( - '--footer', '-he', help='Footer file name', default="footer.png", required=False) + '--footer', '-he', help='Footer file name', default="rehlingen.png", required=False) argparse.add_argument( '--header-offset', '-ho', help='Header offset', default=0, required=False, type=int) @@ -30,7 +30,7 @@ lastname = row['Nachname'] text = lastname.upper() sponsor = row['Sponsor'] - header_file = "sponsor/" + sponsor + ".png" + header_file = sponsor + ".png" output_file = output_folder + "/" + lastname + "_" + firstname + ".png" print("Generating bib for", firstname, lastname, "with sponsor", sponsor) bib_generator(text, output_file, header_file, footer_file, font_file, header_offset) diff --git a/requirements.txt b/requirements.txt index a1e10d3..a5f581a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,6 @@ openpyxl==3.1.2 pandas==2.2.0 pillow==10.2.0 -img2pdf==0.5.1 \ No newline at end of file +img2pdf==0.5.1 +fastapi==0.105.0 +uvicorn[standard]==0.25.0 \ No newline at end of file diff --git a/server.py b/server.py new file mode 100644 index 0000000..9c93d22 --- /dev/null +++ b/server.py @@ -0,0 +1,62 @@ +from fastapi import FastAPI, Response +from typing import Optional +from os import walk +from fastapi.staticfiles import StaticFiles +from bib import generate_image + +app = FastAPI(title="DLV", docs_url="/swagger", + openapi_url="/swagger-json", redoc_url=None) + +app.mount("/header", StaticFiles(directory="header"), name="header") +app.mount("/footer", StaticFiles(directory="footer"), name="footer") + + +def headers(): + header = [] + for (dirpath, dirnames, filenames) in walk("header"): + filenames = [f for f in filenames if f.endswith( + '.png') or f.endswith('.jpg')] + header.extend(filenames) + break + return header + + +def footers(): + footer = [] + for (dirpath, dirnames, filenames) in walk("footer"): + filenames = [f for f in filenames if f.endswith( + '.png') or f.endswith('.jpg')] + footer.extend(filenames) + break + return footer + + +@app.get("/", responses={ + 200: { + "content": {"image/png": {}} + } +}, response_class=Response) +def generate_bib( + text: str, + header: str, + footer: str, + header_offset: Optional[int] = 60, +): + # check if header and footer are valid + if header not in headers(): + return {"error": "Header not found"} + if footer not in footers(): + return {"error": "Footer not found"} + image = generate_image(text, header, footer, + "agency_fb.ttf", header_offset) + return Response(content=image, media_type="image/png", headers={"Content-Disposition": "filename=" + text + ".png"}) + + +@app.get("/headers") +def get_headers(): + return headers() + + +@app.get("/footers") +def get_footers(): + return footers()