조회 수 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
번호 분류 제목 글쓴이 날짜 조회 수
78 코드 폰트를 자동 설치하는 코드 1 네모 2018.07.16 1020
77 코드 파이썬을 이용한 텔레그램 새 글 알림 (허접합니다) 4 이니스프리 2017.11.19 2561
76 코드 파이선 셸에서 실행하면...? 3 제르엘 2018.07.22 595
75 코드 클라이언트단에서 이미지 리사이징 6 file 네모 2018.05.06 1174
74 코드 컴퓨터의 uuid 얻기 5 title: 황금 서버 (30일)humit 2018.01.28 1292
73 코드 잘못 쓰면 컴퓨터가 날아가는 코드 29 제르엘 2018.07.08 1047
72 자료 이게 팔릴까 - Xe/라이믹스 에러페이지 [2017-10-04] 3 file title: 열려라 맛스타의 자물쇠TVJ 2017.10.04 773
71 코드 유튜브에 약간의 기능을 추가 해주는 크롬 확장 프로그램. 11 file Hanam09 2018.01.26 1101
70 코드 엑셀파일 불러서 히스토그램 그려주는 함수 국내산라이츄 2017.08.03 887
69 코드 엑셀 읽어서 그래프 그려주는 함수 1 국내산라이츄 2017.08.03 1576
68 코드 아주 간단한 기초 C++ 6 제르엘 2018.04.21 598
67 자료 소셜XE / 기존 통합 로그인 스킨 V2.2 2 file NoYeah 2017.06.28 1096
66 코드 세린서버에서 시도중인 백업 스크립트 입니다. 4 NoYeah 2017.06.27 856
65 코드 새 글 자동 댓글 스크립트 (AutoHotkey) 9 이니스프리 2017.11.26 3590
64 코드 사이트 서버 이전 (또는 미러링 사이트 구축) 쉽게하는 스크립트 1 NoYeah 2018.01.14 1124
63 코드 브라우저 언어에 따라 다른 폴더를 사용하는 PHP 코드 4 file 네모 2017.10.10 936
62 코드 미완성 받아쓰기 (C) title: 대한민국 국기gimmepoint 2018.04.20 587
61 코드 매우 특이한 버그 9 title: 대한민국 국기gimmepoint 2018.06.05 777
60 자료 링크 파싱 애드온용 스킨 (트위터 스타일) 3 file SNAX 2017.10.03 634
59 코드 도박 중독자를 위한 광고 차단 규칙 file 제르엘 2020.08.21 471
Board Pagination Prev 1 2 3 4 Next
/ 4