소스 공유

|  나만의 소스나 나만 알고 있는 좋은 소스를 서로 공유하는 자리입니다.

조회 수 52 추천 수 0 댓글 0
Atachment
첨부 '2'
?

단축키

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
번호 분류 제목 글쓴이 날짜 조회 수
66 코드 [Python] 싸이월드 미니홈피 백업 스크립트 이니스프리 2019.11.07 67
» 코드 [Python] PIL을 이용한 Animated GIF의 리사이징 file 이니스프리 2019.11.03 52
64 코드 [PyQt] sir.kr에서 스크랩한 게시글을 보여주는 윈도우앱 (검색 및 정렬 가능) 2 file 이니스프리 2019.08.09 155
63 코드 [아미나] Dropbox API를 이용한 이미지 호스팅 보드스킨 11 file 이니스프리 2019.07.13 265
62 코드 [Python] 네이버 모바일 이미지 검색에서의 이미지 파일을 멀티스레드로 다운받고 1개의 파일로 병합 10 file 이니스프리 2019.07.12 220
61 코드 [PHP/Javascript] 아미나에 자동으로 게시글을 생성하고 Ajax로 전송하여 결과를 표시하기 2 file 이니스프리 2019.07.09 108
60 코드 [Python] Selenium을 이용하여 특정 element를 캡처하는 스크립트 2 file 이니스프리 2019.07.03 249
59 코드 [Python] 선택한 파일을 Dropbox API를 이용하여 업로드하고 공유링크를 받아서 이미지 호스팅 용도로 URL을 변환하기 1 file 이니스프리 2019.07.02 179
58 자바스크립트 미니 시계 7 도토리묵 2019.02.22 379
57 코드 [JS]클라이언트에서 Ip를 얻어보자 2 Hanam09 2019.01.21 286
56 코드 [JS] http를 https로 리디렉션! 3 Hanam09 2018.12.30 331
55 코드 [PHP] 이미지를 원하는 크기(원본비율 유지)로 리사이즈 하여 출력 (원본 이미지는 수정하지 않습니다) 6 이니스프리 2018.12.20 1526
54 코드 [아미나] 네이트 실시간 검색어 순위 위젯 (아미나 캐시 적용) 3 file 이니스프리 2018.12.18 323
53 코드 [아미나] 출석 여부를 나타내는 메인화면 위젯 4 file 이니스프리 2018.12.15 338
52 코드 [PHP] 간단한 캐싱 클래스 3 title: 황금 서버 (30일)humit 2018.12.06 294
51 코드 [Python] 텔레그램을 이용한 게시판 새 글 알림봇 7 이니스프리 2018.12.02 937
50 코드 [아미나] 게시글을 작성하면 ID와 IP로 필터링하여 자동으로 랜덤 댓글을 남기기 (+랜덤 포인트) 7 file 이니스프리 2018.11.18 212
49 코드 [PHP] 그누보드 자동 게시글 작성 - 일본기상협회의 우리나라 날씨를 크롤링한 후 파파고로 번역하여 글 작성 4 file 이니스프리 2018.11.15 216
48 코드 [PHP] 기상청 RSS 시간별 예보 위젯 - cache 적용(?) 9 file 이니스프리 2018.10.28 188
47 코드 [오토핫키] 브라우저를 열어 지난번과 동일한 폴더에 MZK를 다운받고 압축을 네이티브로 해제하는 스크립트 file 이니스프리 2018.10.20 286
목록
Board Pagination Prev 1 2 3 4 Next
/ 4
서버에 요청 중입니다. 잠시만 기다려 주십시오...