본문 바로가기

기타 프로젝트

[해커네컷] 1. 기존 코드 리팩토링

어쩌다가 다시 꺼내게 되었나

AWS 해커톤이 성공적으로 끝났다. 당연히 해커네컷도 사용이 끝났다. 아쉬움과 섭섭함이 남았다. 해커톤이 시작하기 전에 해커네컷을 완성하려고 아등바등 하던 일주일이 눈앞에 아른거렸다.

 

그러던 중 너무 반가운 소식이 들렸다. 

짜릿했다. 사용료도 사용료지만 내가 만든 어플이 더 많은 사람들한테 사용될 수 있다는 사실이 행복했다. 동시에 만들어 놓은 해커네컷을 그냥 쓰는 게 아니라 더 고도화 시켜 보고 싶다고 생각했다. 해커네컷을 실제로 사용했을 때 불편했던 부분과 구현하고 싶었지만 시간이 부족해서 구현하지 못했던 부분을 해결해 보고 싶었다.

 

 

리팩토링은 처음이라

 기존의 코드를 다시 훑어봤다. 개판이었다. 써내려갔다기보다는 덕지덕지 발랐다는 표현이 더 적절할 지경이었다. 내가 읽어도 코드가 어떻게 짜여져 있는지를 알 수가 없었다. 내가 읽어도 이 모양인데 남이 읽으면 얼마나 난잡해 보일까 싶었다. 그러던 와중 예전에 친구가 했던 말이 생각났다. 어떻게든 실행되는 코드를 짜는 것보다 코드를 아름답게 짜는 게 더 중요하다고 했었다. 결국 짜 놓은 코드를 다시 예쁘게 짜 보기로 했다. 인생 처음 짜 둔 코드를 다시 읽는 순간이다.

 

 아래는 기존의 코드이다.

 

from PIL import Image
import time
import config
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
import re
import os
import requests
import logging


app = App(token=config.bot_token)
slack_client = WebClient(token=config.bot_token)

logging.basicConfig(filename='./log.txt', level=logging.DEBUG)

@app.message(re.compile("찍어줘"))
def show_(message, say, body, client):
    message = {
        "blocks":[
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": f"*카메라 꺼내는 중...*"
                }
            },
        ]
    }
    


    try:
        img_files = body["event"]["files"]
        print(img_files)
        if(len(img_files) != 4):
            say("네 컷 사진을 올려 주세요")
        else:
            file_srces = []
            say("카메라 꺼내는 중...")
            for img in img_files:
                url = img["url_private_download"]
                img_name = img["name"]
                image_response = requests.get(url, headers={"Authorization": f"Bearer {config.bot_token}"}, stream=True)
                file_src = f'./base_imgs/base_{img_name}'
                file_srces.append(file_src)
                with open(file_src, 'wb') as f:
                    for chunk in image_response.iter_content(1024):
                        f.write(chunk)
            final_src = gogo_picture(file_srces)
            say("사진 찍는 중...")
            filepath = final_src # Your filepath
            # channel_id = body["container"]["channel_id"]
            # channel_name = body["channel"]["name"]
            response = client.files_upload(channels = "gogo", file=filepath)
            message = {
            "blocks":[
                {
                    "type": "section",
                    "text": {
                        "type": "mrkdwn",
                        "text": f"*찰칵찰칵📷📷*"
                    }
                },
                ]
            }
            say(message)
    except:
        say("사진 네 장과 함께 '찍어줘'라고 말씀해 보세요")

    
@app.action("say_gogo")
def say_gogo(ack, body, message, say, client):
    ack()
    say("가보자고")
    filepath = "./frame.png" # Your filepath
    channel_id = body["container"]["channel_id"]
    channel_name = body["channel"]["name"]
    response = client.files_upload(channels = channel_name, file=filepath)
    message = {
        "blocks": [
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": f"*찰칵찰칵*"
                }
            }
        ]
    }

    say(message)
    print(body["channel"]["name"])


def gogo_picture(fore_img_srces):
    back_img_src = "./frame.png"
    back_img = Image.open(back_img_src)
    fore_imgs = []

    for img_src in fore_img_srces:
        now_img = Image.open(img_src)
        if(now_img.width<now_img.height*1080/725):
            now_img = now_img.crop((0, now_img.height*1/2-725/1080*now_img.width*1/2, now_img.width, now_img.height*1/2+725/1080*now_img.width*1/2))
            now_img = now_img.resize((1080, 725))
        else:
            now_img = now_img.crop((now_img.width*1/2-1080/725*now_img.height*1/2, 0, now_img.width*1/2+1080/725*now_img.height*1/2, now_img.height))
            now_img = now_img.resize((1080, 725))
        fore_imgs.append(now_img)

    back_img = back_img.resize((2386, 3602))

    new_img = Image.new("RGB", back_img.size, "#ffffff")

    new_img.paste(back_img)
    new_img.paste(fore_imgs[0], (58, 380))
    new_img.paste(fore_imgs[1], (58, 1148))
    new_img.paste(fore_imgs[2], (58, 1921))
    new_img.paste(fore_imgs[3], (58, 2694))
    new_img.paste(fore_imgs[0], (58+1190, 380))
    new_img.paste(fore_imgs[1], (58+1190, 1148))
    new_img.paste(fore_imgs[2], (58+1190, 1921))
    new_img.paste(fore_imgs[3], (58+1190, 2694))
    final_src = f"./final_img/final_img_{time.time()}.png"
    new_img.save(final_src)
    print(new_img.size)
    return final_src



if __name__ == '__main__':
    SocketModeHandler(app, config.app_token).start()
    logging.debug('debug')
    logging.info('info')
    logging.warning('warning')
    logging.error('error')
    logging.critical('critical')

 

 

 

해커네컷의 구조

 '해커네컷'의 전체적인 구조는 다음과 같다.

 

모듈화 시키기

생각해 둔 껍데기는 다음과 같다.

더보기
when triggered "찍어줘"{
	photo_card = get_photo_card(message);
    send_message(photo_card);
}

get_photo_card(message){
	src_imgs = pull_out_imgs_from(message);
    rendered_imgs = render_imgs(src_imgs);
    photo_card = get_photo_card(renderd_imgs);
}

send_message(photo_card){
	channel = get_channel_id();
    send photo_card to channel;
}

 

 

 

 

코드 리팩토링

리팩토링한 코드 전체

더보기
from PIL import Image
import time
import config
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
from slack_sdk import WebClient
import re
import requests

app = App(token=config.bot_token)
slack_client = WebClient(token=config.bot_token)


### main function ###
@app.message(re.compile("찍어줘"))
def fourcut(client, body, say):
    say("카메라 꺼내는 중...")
    photo_card_src = make_photo_card(body)
    here = parse_channel_id(body)
    say("사진 찍는 중...")
    send_img_to_channel(photo_card_src, here, client, say)


### parse channel id from body, and then return it ###
def parse_channel_id(body):
    return body['event']['channel']


### whole sequence of making photo card, and tnen return src of that photo card ###
def make_photo_card(body):
    try:
        file_objs = parse_file_object(body)
        img_srces = save_imgs_from(file_objs)
        photo_card_src = paste_imgs_to_frame(img_srces)
        return photo_card_src
    except:
        return None


### parse file object from body ###
def parse_file_object(body):
    return body["event"]["files"]
        

### request img from salck server and write file in local, and thel return src of that ###
def save_imgs_from(file_objs):
    file_srces = []

    for obj in file_objs:
        url = obj["url_private_download"]
        img_name = obj["name"]
        file_src = f'./base_imgs/base_{img_name}'
        file_srces.append(file_src)
        image_response = requests.get(url, headers={"Authorization": f"Bearer {config.bot_token}"}, stream=True)
        
        with open(file_src, 'wb') as f:
            for chunk in image_response.iter_content(1024):
                f.write(chunk)
    return file_srces
    

### paste imgs to frame ###
def paste_imgs_to_frame(img_srces, frame_src):
    frame_src = './frame.png'
    frame = Image.open(frame_src).resize((2386, 3602))
    imgs = get_pil_imgs_at(img_srces)
    resized_imgs = get_resized_imgs(imgs)
    new_img = Image.new("RGB", frame.size, "#ffffff")
    new_img.paste(frame)
    for i in range(4):
        start_point_y = 375
        start_point_x_left = 58
        start_point_x_right = 1250
        h_factor = 773
        new_img.paste(resized_imgs[i], (start_point_x_left, start_point_y+i*h_factor))
        new_img.paste(resized_imgs[i], (start_point_x_right, start_point_y+i*h_factor))
    final_src = f"./final_img/final_img_{time.time()}.png"
    new_img.save(final_src)
    return final_src


## return pil object at src which recieved ###
def get_pil_imgs_at(img_srces):
    imgs = []
    for img_src in img_srces:
        now_img = Image.open(img_src)
        imgs.append(now_img)
    return imgs


### recieve bulk of imgs and return them ###
def get_resized_imgs(imgs):
    resized_imgs = []
    for img in imgs:
        resized_img = resize_img(1080, 725, img)
        resized_imgs.append(resized_img)
    return resized_imgs


### resize target_img ###
def resize_img(width, height, target_img):
    now_img = target_img
    if(now_img.width<now_img.height*1080/725):
            now_img = now_img.crop((0, now_img.height*1/2-725/1080*now_img.width*1/2, now_img.width, now_img.height*1/2+725/1080*now_img.width*1/2))
            now_img = now_img.resize((1080, 725))
    else:
        now_img = now_img.crop((now_img.width*1/2-1080/725*now_img.height*1/2, 0, now_img.width*1/2+1080/725*now_img.height*1/2, now_img.height))
        now_img = now_img.resize((1080, 725))
    return now_img


### send img at src to channel which has same id as received channel_id ###
def send_img_to_channel(img_src, cannel_id, client, say):
    if(img_src != None):
        response = client.files_upload_v2(channel = cannel_id, file=img_src)
        if(response['ok']==True):
            say("찰칵찰칵📸📷")
        else:
            say("다시 시도하세요.")
    else:
        say("사진 네 장과 함께 '찍어줘'라고 말해 보세요")


### run the app ###
if __name__ == '__main__':
    SocketModeHandler(app, config.app_token).start()