조회 수 1356 추천 수 0 댓글 0
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄 첨부
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄 첨부
Extra Form
라이선스 기타(따로 작성)


1. 스크립트를 작성하게 된 계기


Animated GIF를 리사이징해보려고 했는데 파이썬이나 PHP로 작성된 스크립트를 찾지 못했습니다.


이러한 스크립트가 흔하지 않은 이유를 생각해보면, 우선 ImageMagick을 사용하면 간단히 해결할 수 있기 때문이겠죠.


또한 JPG나 PNG와 달리 GIF 움짤의 경우에는 일반적으로 커뮤니티 사이트에 올라오는 이미지 사이즈가 너무 커서 


이를 부득이 리사이징해야 되는 경우는 많지 않을 것으로 생각됩니다. 



2. 스크립트


파이썬을 이용한 여러가지 방법이 있겠지만 PIL 라이브러리만 사용했으며, 


그 중에서도 PIL을 이용하여 프레임을 하나씩 변환해주는 방식을 채택했습니다.


이 과정에서 thumbnail 메서드를 사용하는 것이 resize 메서드를 사용하는 것보다 안정적이라고 하더군요.


LANCZOS 필터를 사용하긴 했지만, 다른 필터를 사용해도 큰 차이를 느끼지는 못했습니다.


덮어쓰기가 되지 않도록 파일명을 변환하는 부분과, animated GIF가 아닌 파일을 입력한 경우 등을 처리하는 부분을 제외하면


PIL을 이용하여 리사이징 프로세싱을 하는 스크립트 자체는 간단합니다.



다만 각 프레임을 프로세싱하여 새로운 이미지 파일을 생성하는 과정을 거치기 때문에


기존 파일의 프레임 사이의 duration에 대한 설정이 날아가게 됩니다.


파이썬에서 GIF 파일의 duration 설정값을 바로 알아내는 방법을 찾아내지는 못했고, 


우회적으로 총 러닝타임을 구하여 이를 프레임으로 나누는 방법을 택했습니다.


다만 테스트 결과 약간의 오차가 발생하는 것은 제 실력으로는 어쩔 수 없었습니다 ㅠㅠ



from PIL import Image
import os

def resize_gif(path, save_as=None, resize_to=None, ratio=None):
    all_frames = extract_and_resize_frames(path, resize_to, ratio)
   
    im = Image.open(path)
    framerate = get_duration_per_frames(im)

    if not save_as:
        number = 1
        if not '\\' in path:
            path = os.getcwd() + '\\' + path
        while os.path.isfile(path):
            save_as = os.path.splitext(path)[0] + str(number) + '.gif'
            if not os.path.isfile(save_as):
                break
            number += 1
        print('The resized filename will be ' + os.path.basename(save_as))

    if len(all_frames) == 1:
        print("Warning : There is only 1 frame.")
        print('The file was resized to ' + str(all_frames[0].size) + '.')
        all_frames[0].save(save_as, optimize=True)
    else:
        print('The file was resized to ' + str(all_frames[0].size) + '.')
        all_frames[0].save(save_as, optimize=True, save_all=True, append_images=all_frames[1:], loop=0, duration=framerate)


def analyseImage(path):
    im = Image.open(path)
    results = {'size': im.size, 'mode': 'full'}
    try:
        while True:
            if im.tile:
                tile = im.tile[0]
                update_region = tile[1]
                update_region_dimensions = update_region[2:]
                if update_region_dimensions != im.size:
                    results['mode'] = 'partial'
                    break
            im.seek(im.tell() + 1)
    except EOFError:
        pass
    finally:
        return results


def extract_and_resize_frames(path, resize_to=None, ratio=None):
    mode = analyseImage(path)['mode']
    im = Image.open(path)
    if not resize_to:
        if not ratio:
            ratio = 0.5
        resize_to = [int(ratio * s) for s in im.size]
    i = 0
    p = im.getpalette()
    last_frame = im.convert('RGBA')
    all_frames = []

    try:
        while True:
            if not im.getpalette():
                im.putpalette(p)
            new_frame = Image.new('RGBA', im.size)
            if mode == 'partial':
                new_frame.paste(last_frame)
            new_frame.paste(im, (0, 0), im.convert('RGBA'))
            new_frame.thumbnail(resize_to, Image.LANCZOS)
            all_frames.append(new_frame)
            i += 1
            last_frame = new_frame
            im.seek(im.tell() + 1)
    except EOFError:
        pass
    finally:
        return all_frames


def get_duration_per_frames(image_object):
    image_object.seek(0)
    duration = 0
    try:
        while True:
            duration += image_object.info['duration']
            image_object.seek(image_object.tell() + 1)
    except EOFError:
        pass
    finally:
        duration_per_frames = round(duration / image_object.n_frames, 2)
        return duration_per_frames

resize_gif('파일명.gif', save_as=None, resize_to=None, ratio=None)



3. 사용례

원래 사이즈: 1730kb


절반 사이즈: 1325kb


ratio 값을 따로 입력하지 않으면 위와 같이 절반 사이즈로 축소됩니다.


다만 PIL 라이브러리가 자체적으로 GIF 압축 기능을 제공하지 않기 때문에,


파일 용량은 그다지 줄어들지 않는 점을 확인할 수 있습니다.


어중간하게 80% 정도의 사이즈로 축소를 하면 오히려 파일 용량이 증가하는 문제가 있습니다.


GIFLossy 알고리즘 등을 사용해야 이 문제를 해결할 수 있을 것 같습니다.



한편 duration과 관련된 문제는 http://gifduration.konstochvanligasaker.se/ 에서 확인을 해보면


두 파일 모두 정확히 3840ms를 기록한 것을 확인할 수 있었습니다.



4. 결론


PIL 라이브러리만을 이용하여 GIF 움짤을 리사이징하는 것은 의외로 간단히 해결되지만, 


(1) GIF 파일의 duration 설정값이 변하는 문제가 있으며, 


(2) 파일 용량을 확실히 감소시킬 목적으로는 GIFLossy를 이용하는 방법을 권장합니다.


다음에 시간이 날 때 PyQt를 이용하여 파일을 Drag & Drop 할 수 있도록 구현해보겠습니다 ^^



List of Articles
번호 분류 제목 글쓴이 날짜 조회 수
63 코드 AWSCLI, in a single file (portable, linux) 1 file Seia 2021.04.10 325
62 코드 [Python-Gnuboard] 파이썬으로 구현한 그누보드 자동 글쓰기 함수 1 file 이니스프리 2021.04.08 1554
61 코드 [Python] 휴일지킴이 약국을 크롤링하여 Folium 지도에 마커로 표시하는 PyQt 윈도우 앱 7 file 이니스프리 2021.03.13 1274
60 코드 도박 중독자를 위한 광고 차단 규칙 file 제르엘 2020.08.21 471
59 코드 [Python] 유튜브 영상을 다운받아 일정 간격으로 캡쳐하여 10장씩 merge하기 3 file 이니스프리 2020.05.27 1291
58 코드 [Python/Telegram] Studyforus 알림봇 (댓글, 스티커 파싱) 7 file 이니스프리 2020.05.15 869
57 코드 [Python] url 주소로부터 IP 주소 알아내기 title: 황금 서버 (30일)humit 2020.02.20 2259
56 코드 [Python] 네이버 실시간 검색어 3 title: 황금 서버 (30일)humit 2020.01.23 1347
55 코드 Koa에서 자동으로 라우팅 채워주기 Seia 2020.01.22 739
54 코드 JavaScript에서 파이썬 문자열 처리 함수 중 하나 (바인딩)를 구현 7 Seia 2020.01.20 578
53 코드 [Python] Google Image Search 결과를 받아오기 file 이니스프리 2019.12.09 1209
52 코드 [파이썬] Requests를 사용한 네이버 카페 크롤링 - 일정수 이상의 리플이 달린 게시글만 텔레그램 알림 3 file 이니스프리 2019.11.17 4376
51 코드 [JS] 클라이언트단 GET Parameter Hanam09 2019.11.16 714
50 코드 [Python] 싸이월드 미니홈피 백업 스크립트 1 이니스프리 2019.11.07 2456
» 코드 [Python] PIL을 이용한 Animated GIF의 리사이징 file 이니스프리 2019.11.03 1356
48 코드 [PyQt] sir.kr에서 스크랩한 게시글을 보여주는 윈도우앱 (검색 및 정렬 가능) 7 file 이니스프리 2019.08.09 1146
47 코드 [아미나] Dropbox API를 이용한 이미지 호스팅 보드스킨 12 file 이니스프리 2019.07.13 1619
46 코드 [Python] 네이버 모바일 이미지 검색에서의 이미지 파일을 멀티스레드로 다운받고 1개의 파일로 병합 11 file 이니스프리 2019.07.12 1557
45 코드 [PHP/Javascript] 아미나에 자동으로 게시글을 생성하고 Ajax로 전송하여 결과를 표시하기 2 file 이니스프리 2019.07.09 963
44 코드 [Python] Selenium을 이용하여 특정 element를 캡처하는 스크립트 2 file 이니스프리 2019.07.03 6170
Board Pagination Prev 1 2 3 4 Next
/ 4