티스토리 뷰

Intro

이전에 Github Actions로 여러가지 자동화를 할 수 있는 것을 이야기 했는데 구체적인 예시를 소개하고자 합니다.
Text 파일 업로드를 하면 해당 텍스트 파일을 기준으로 이미지를 생성해서 업로드 하는 Workflow입니다. 이번 포스팅에서는 각 줄을 좀 더 자세히 하나하나 설명을 적어보았습니다.

작동방식

텍스트 파일을 깃허브 레포에 커밋하면 파이썬 코드가, 해당 텍스트파일을 가지고 이미지를 만들도록 할 것입니다.

어떻게 되는지 한번 보여드리겠습니다.

아래 처럼 텍스트 파일을 만들어서 Push 하면..

Push Text Files

 

Github Action이 돌아갑니다.

Github Action

 

이미지 파일이 만들어집니다.

와우.png

조금 긴 텍스트는 잘리네요. 하지만 파이썬 코드를 수정해야 할 것 같은데 이번에는 넘어가겠습니다.

깃허브 액션으로 이미지

파이썬 코드

이 코드는 현재 디렉토리의 .txt 파일 이름을 이용해 최대 3줄로 줄바꿈한 텍스트를 포함하는 128x128 크기의 검은색 썸네일 이미지를 생성하고, 이를 thumbnails 폴더에 저장합니다. 텍스트는 흰색으로 중앙에 배치되며, 텍스트 크기는 이미지에 맞도록 조정됩니다. 코드 실행 시 폴더에 이미 해당 이름의 이미지 파일이 있다면 새로 생성하지 않고 넘어갑니다. 최종적으로, .txt 파일마다 해당 텍스트가 표시된 썸네일이 저장되는 구조로 작동합니다.

한 줄씩 보겠습니다.

import os
from PIL import Image, ImageDraw, ImageFont
  • os: 파일과 디렉토리 작업을 위한 라이브러리
  • PIL.Image, PIL.ImageDraw, PIL.ImageFont: 이미지를 생성하고 글자를 그리기 위한 Pillow 라이브러리 모듈들입니다.
thumbnails_path = './thumbnails'
os.makedirs(thumbnails_path, exist_ok=True)
  • thumbnails_path 변수에 썸네일 저장 경로를 지정합니다.
  • os.makedirsthumbnails 폴더를 생성하는데, exist_ok=True 옵션 덕분에 폴더가 이미 존재해도 오류 없이 넘어갑니다.
for file in os.listdir('.'):
    if file.endswith('.txt'):
  • 현재 디렉토리에서 .txt 확장자로 끝나는 파일을 하나씩 처리합니다.
filename = file.replace('.txt', '')
thumbnail_file = f'{thumbnails_path}/{filename}.png'
  • 파일 이름에서 .txt 확장자를 제거하여 filename 변수에 저장하고, 썸네일 파일명을 thumbnail_file에 저장합니다.
if os.path.exists(thumbnail_file):
    print(f"'{thumbnail_file}' 파일이 이미 존재하여 건너뜁니다.")
    continue
  • thumbnail_file 경로에 파일이 이미 존재하면 메시지를 출력하고, 다음 파일로 넘어갑니다.
img = Image.new('RGB', (128, 128), color='black')
d = ImageDraw.Draw(img)
  • img라는 128x128 크기의 검은색 이미지를 생성하고, d 객체로 이미지 위에 텍스트를 그릴 준비를 합니다.
font_size = 24
font = ImageFont.load_default()
  • font_size를 24로 초기화하고, 기본 시스템 폰트를 font 변수에 할당합니다.
max_width, max_height = 128, 128
text = filename
  • 이미지 크기(max_width, max_height)를 설정하고, 텍스트에 파일 이름을 지정합니다.
def wrap_text(text, font, max_width):
  • 텍스트를 줄바꿈하여 이미지에 맞추기 위한 wrap_text 함수를 정의합니다.
words = text.split()
lines = []
current_line = ""
  • 파일 이름 텍스트를 단어별로 나누고, 줄을 저장할 리스트 lines와 현재 줄을 나타낼 current_line을 초기화합니다.
for word in words:
    test_line = f"{current_line} {word}".strip()
  • 각 단어를 추가하며 줄이 최대 너비를 넘지 않는지 확인하기 위해 test_line 변수에 저장합니다.
bbox = d.textbbox((0, 0), test_line, font=font)
width = bbox[2] - bbox[0]
  • textbbox 메서드로 텍스트의 너비를 계산합니다.
if width <= max_width:
    current_line = test_line
else:
    lines.append(current_line)
    current_line = word
lines.append(current_line)
return lines[:3]
  • 줄의 너비가 max_width보다 작으면 현재 줄에 단어를 추가하고, 초과하면 lines 리스트에 저장하고 새로운 줄을 시작합니다. 최대 3줄까지만 반환합니다.
while font_size > 8:
    font = ImageFont.truetype("fonts/BMDOHYEON_ttf.ttf", font_size)
  • font_size를 줄여가며 텍스트가 이미지에 맞을 때까지 폰트를 설정합니다. (최소 8 포인트로 제한)
wrapped_text = wrap_text(text, font, max_width)
  • wrap_text 함수를 호출하여 줄바꿈한 텍스트를 wrapped_text 변수에 저장합니다.
text_height = sum(d.textbbox((0, 0), line, font=font)[3] - d.textbbox((0, 0), line, font=font)[1] for line in wrapped_text)
if text_height <= max_height:
    break
font_size -= 1
  • 텍스트의 총 높이를 계산하여 이미지 높이보다 작아질 때까지 폰트 크기를 줄입니다.
total_text_height = sum(d.textbbox((0, 0), line, font=font)[3] - d.textbbox((0, 0), line, font=font)[1] for line in wrapped_text)
y_offset = (max_height - total_text_height) // 2
  • 줄바꿈한 텍스트의 총 높이를 기준으로 수직 중앙에 배치할 y_offset을 계산합니다.
for line in wrapped_text:
    bbox = d.textbbox((0, 0), line, font=font)
    text_width = bbox[2] - bbox[0]
    text_height = bbox[3] - bbox[1]
    position = ((max_width - text_width) // 2, y_offset)
    d.text(position, line, fill=(255, 255, 255), font=font)
    y_offset += text_height
  • 각 줄을 이미지 중앙에 맞추어 그린 후, y_offset을 업데이트하여 다음 줄의 위치를 설정합니다.
img.save(thumbnail_file)
print(f"'{thumbnail_file}' 파일을 새로 생성했습니다.")
  • 최종 이미지를 thumbnail_file 경로에 저장하고, 생성 메시지를 출력합니다.

WorkFlow

이 GitHub Actions 워크플로우는 저장소에 .txt 파일이 추가되거나 변경될 때마다 트리거되어, 해당 파일들을 기반으로 썸네일 이미지를 생성한 뒤 thumbnails 폴더에 저장하고, 생성된 썸네일 이미지를 저장소에 커밋하여 푸시합니다. 워크플로우는 우분투 환경에서 실행되며, Python을 사용하여 이미지 생성 스크립트를 수행합니다. 이후 생성된 썸네일 이미지들을 저장소에 자동으로 커밋하고 푸시하여 저장소에 반영되도록 합니다.

on:
  push:
    paths:
      - '**/*.txt'
  • .txt 파일이 저장소에 추가되거나 수정될 때마다 이 워크플로우가 자동으로 실행되도록 트리거를 설정합니다. 모든 디렉토리와 서브 디렉토리에서 .txt 파일의 변경 사항을 감지합니다.
jobs:
  generate_thumbnail:
    runs-on: ubuntu-latest
  • generate_thumbnail이라는 작업을 정의하고, 우분투 최신 버전 환경에서 실행되도록 설정합니다.
permissions:
  contents: write
  • 이 작업에서는 저장소의 파일을 수정하고 커밋할 권한이 필요하므로 write 권한을 부여합니다.
steps:
  - name: Checkout repository
    uses: actions/checkout@v2
  • 첫 번째 단계로, actions/checkout 액션을 사용하여 저장소의 코드를 현재 워크플로우 실행 환경에 가져옵니다.
  - name: Set up Python
    uses: actions/setup-python@v2
    with:
      python-version: '3.x'
  • Python 3.x 버전을 설정하여 이후 파이썬 스크립트를 실행할 준비를 합니다.
  - name: Install dependencies
    run: |
      pip install pillow
  • Pillow 라이브러리를 설치합니다. Pillow는 Python에서 이미지 처리를 할 수 있게 도와주는 라이브러리로, 이 워크플로우에서 썸네일 이미지를 생성하는 데 사용됩니다.
  - name: Generate Thumbnail
    run: |
      python scripts/generate_thumbnail.py
  • generate_thumbnail.py 스크립트를 실행하여 .txt 파일을 기반으로 썸네일 이미지를 생성합니다. 해당 스크립트는 scripts 폴더에 위치해야 합니다.
  - name: Commit and push thumbnails
    run: |
      git config --local user.name "github-actions[bot]"
      git config --local user.email "github-actions[bot]@users.noreply.github.com"
      git add thumbnails/
      git commit -m "Add thumbnails for text files"
      git push
  • 썸네일 생성 후, Git 설정을 통해 github-actions[bot] 사용자로 커밋 작성자를 설정합니다.
  • git add thumbnails/를 통해 thumbnails 폴더의 변경 사항을 추가하고, git commit으로 커밋을 생성한 뒤, git push로 저장소에 푸시하여 생성된 썸네일이 저장소에 반영되도록 합니다.

마무리

사실 간단해 보이지만 만드는과정에서 많은 실패가 일어납니다. Workflow에 권한이 없다느니, 파이썬 파일 실행과정에서의 문제 등으로 인해 수많은 fail을 거쳐 성공을 할 수 있었습니다.

Pass & Fails...

이상으로 포스팅을 마칩니다.

반응형
댓글