Pythonで画像をスライドショー(GIFアニメ)にするGUIアプリを作ってみた

PythonとTKinter、Pillowを使って画像をスライドショー(GIFアニメ)にするGUIアプリを作ってみました。

## アプリの目的

・余った画像をスライドショー化して有効活用すること。スライドショーにしたGIFアニメは、SNSに投稿するなどして広告として利用する。

・オンラインではなく、ローカル環境でスライドショー化すること。※Webツールが安全でないこともあるため。

・ボタン一発で複数の画像をまとめてスライドショー(GIFアニメ)にすること。※動画編集ソフトを使うのが面倒なため。


## アプリの使い方

### 1.スライドショーの「幅」を指定する

スライドショー(GIFアニメ)の「幅」を指定します。数値はスライダーで調整します。

例：大きめのサイズ　800 x 600　(Facebook、Instagaramに最適)

　　小さめのサイズ 400 x 300 (Twitterに最適)

### 2.スライドショーの「高さ」を指定する

スライドショー(GIFアニメ)の「高さ」を指定します。数値はスライダーで調整します。

### 3.スライドショーにしたいフォルダを選択する

スライドショーにしたいフォルダ（複数の画像が入っているもの）を選択します。フォルダはダブルクリックで開いた状態にしてください。

### 4.RUNボタンを押す（保存ファイルを確認）

最後にRUNボタンを押すとGIFアニメのファイル「slideshow_swipe.gif」がホームディレクトリに作成されます。

ファイルが作成されるまで、少し時間がかかります。

RUNボタンが押したままになっている状態では、まだファイルが作成されていません。

再びRUNボタンが押せる状態になったら、ファイルが出来上がっています。

Windowsでexe化した場合は、（「dist」フォルダ内にある）アプリのフォルダ内にファイルが作成されています。

なお、作成されたGIFアニメは短い動画ですが、ファイル形式は「画像」扱いです。したがって、画像ビューアーで開きます。

## 用意するもの

### 画像ファイルが入ったフォルダ

フォルダの中に複数の画像を入れておいてください。※jpgやpngなどのファイル。

### Pillowをインストール

Pillowをインストールしておきます。
```
pip install pillow
```

## アプリのコード

アプリのコードを実行することで、GUIアプリが自動で起動します。

```python:slideshow-no-music.py
# coding: utf-8
# インポート
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
import os
from PIL import Image, ImageDraw

### 関数 ###
# Browse...
def browse_func():
    # グローバル変数の宣言
    global dir_path
    dir = '/home/'
    #フォルダを選択する
    dir1 = filedialog.askdirectory(initialdir = dir) 
    #フォルダ名（ディレクトリ名）を取得
    dir_path = str(dir1 + '/')

# RUN
def run_func():
    global dir_path
    # フォルダ内のファイルをリストに
    files = os.listdir(dir_path)
    # 1番目のファイルをリストの最後に追加
    files.append(files[0]) 
    # スライドショーの幅、高設定
    s_width, s_height = 800, 550
    # スライドショーのアスペクト比(高さ/幅)
    s_aspect = s_height/s_width
    # gifファイル作成用イメージリスト
    images =[] 
    # サイズ調整した画像の一時保存用リスト
    temp = []
    #try:
         # 画像をサイズ調整しリストに追加
    for i in range(len(files)):
            # 画像の読み出し
            im = Image.open(dir_path + files[i])
           # 画像幅、高さの取得、アスペクト比(高さ/幅)の算出
            width = im.width
            height = im.height
            aspect = height/width
            # 画像のアスペクト比がスライドショーと同じ場合そのままスライドショーサイズにリサイズ
            if aspect == s_aspect:
              resize = im.resize((s_width, s_height))            
           # 画像のアスペクト比がスライドショーより大きい場合上下をカットしスライドショーサイズにリサイズ
            if aspect > s_aspect:
              crop_size = int((height-s_aspect*width)/2)
              crop = im.crop((0, crop_size, width, height-crop_size))
              resize = crop.resize((s_width, s_height))    
           # 画像のアスペクト比がスライドショーより小さい場合左右をカットしスライドショーサイズにリサイズ
            if aspect < s_aspect:
              crop_size = int((width-height/s_aspect)/2)
              crop = im.crop((crop_size, 0, width-crop_size, height))
              resize = crop.resize((s_width, s_height))
            # リサイズした画像をリストに追加    
            temp.append(resize)

        # リストから画像を読みだしスライドショーに
    for i in range(len(files)-1):
            # 画像の読み出し
            im = temp[i]
            # 次の画像の読み出し
            im2 = temp[i+1] 
            # gifファイルに画像を20コマ追加    
            for j in range(20):
                images.append(im)  
            # 次の画像とスワイプして画像をゆっくり切り替える    
            for j in range(30):
                mask = Image.new('1', (s_width, s_height))
                draw = ImageDraw.Draw(mask)
                draw.rectangle([(int(j/29*s_width), 0), (s_width, s_height)], 
                        fill='white', outline='white', width=1)
                blend = Image.composite(im, im2, mask)
                images.append(blend)
    # gif動画保存
    images[0].save('slideshow_swipe.gif', save_all=True, append_images=images[1:], 
               optimize=False, duration=50, loop=0)  
    #except:
        # ステータスの更新
        #statusbar["text"] = " Error!!"



### GUI  ###
# ウインドウの作成
root = tk.Tk()
# ステータスバー設置 
statusbar = tk.Label(root, text =  " Here we go!!",
                     bd = 1, relief = tk.SUNKEN, anchor = tk.W)
statusbar.pack(side = tk.BOTTOM, fill = tk.X)
# 実行ボタン
run_button = tk.Button(root, text = "RUN", command = run_func)
run_button.pack(pady = 10, ipadx = 20, side = tk.BOTTOM)
# ラベルフレーム
frame = ttk.Labelframe(root, text = "Select Extension", padding = 10)
frame.pack(padx = 20, pady = 5, side = tk.BOTTOM)
# コンボボックス
extensions = [".jpg", ".jpeg", ".png", ".gif", ".bmp"]
cb = ttk.Combobox(frame, values = extensions)
cb.pack(side = tk.LEFT)
# 参照ボタン
browse_button = tk.Button(frame, text = "Browse...",
                          command = browse_func)
browse_button.pack(padx = 10, side = tk.LEFT)

# ウインドウ状態の維持
root.mainloop()
```

## 作成したアプリをexe化する

pyinstallerを使って、アプリをexe化することができます。exe化すると、ボタンをダブルクリックするだけでアプリを起動できるようになります。

```
pip install pyinstaller
```

```
pyinstaller slideshow-no-music.py
```

Linux Ubuntuだと非常に簡単にexe化できます。

Windowsはけっこう難しかったです。詳しい手順は以下の記事にまとめてあります。

Python デスクトップアプリ開発に挑戦してみた！ https://zenn.dev/akibaburari/articles/32a89b19fcf889

## SNSに投稿する

作成したスライドショー（GIFアニメ）はSNSに投稿するなどして有効活用します。

自分はこんな感じで使っています。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">楽天市場に入荷！30,800円。「Teclast M50 Pro」はクーポンで20,900円、「Fire Max 11」は26,200円、「Lenovo Tab B10 3rd Gen」は23,870円で販売中。<br><br>「aiwa tab AS10 (6)」大容量でお得？ 高コスパな人気タブレットと徹底 比較！ | 秋葉原ぶらり <a href="https://t.co/hvXc2RGh1q">https://t.co/hvXc2RGh1q</a><a href="https://twitter.com/hashtag/tablet?src=hash&amp;ref_src=twsrc%5Etfw">#tablet</a> <a href="https://twitter.com/hashtag/%E3%82%BB%E3%83%BC%E3%83%AB?src=hash&amp;ref_src=twsrc%5Etfw">#セール</a> <a href="https://twitter.com/hashtag/aiwa?src=hash&amp;ref_src=twsrc%5Etfw">#aiwa</a> <a href="https://t.co/8gSPOA9vwl">pic.twitter.com/8gSPOA9vwl</a></p>&mdash; BURARI (@akiba_burari) <a href="https://twitter.com/akiba_burari/status/1699619374712455412?ref_src=twsrc%5Etfw">September 7, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



### Twitterの場合

TwitterでGIFアニメを投稿する場合、容量に制限があります。

パソコンで試してみたところ、15MBまで投稿できました。

高画質のGIFアニメを投稿したい場合は、画像3枚ぐらいをスライドショー化するといいです。

それ以上の画像を使用すると、ファイルが重たくなり、容量制限にひっかかりやすくなります。

※Twitterの場合、GIFからMP4に変換した方が容量が少なくなり、投稿しやすくなるのかもしれません。GIFからMP4に変換するコードはこの後に記載しています。

### Facebookの場合

Facebookに20MB以上のGIFアニメを投稿してみたところ、問題なく表示されました。

TwitterのようにGIFアニメとして表示されるわけではなく、「動画」ファイルとして扱われているようです。

調べてみると、動画広告としてGIFアニメを使用する場合は「4GB未満」にする必要があるそうです。

## Instagaramの場合

Instagaramの場合はGIFアニメを投稿できません。対応するファイル形式がMP4・MOVのためです。

したがって、GIFからMP4に変換する必要があります。

GIFからMP4に変換するサービスは以下のようなものがあります。

GIF MP4 変換。オンライン フリー — Convertio https://convertio.co/ja/gif-mp4/

GIF動画をMP4に変換｜Adobe Express https://www.adobe.com/jp/express/feature/video/convert/gif-to-mp4

Pythonで変換することもできるようです。

【Python】gif形式の動画をmp4形式に変換する方法！ - ヒガサラblog https://www.higashisalary.com/entry/python-gif-to-mp4

MoviePyを使用しPythonでGIF画像をビデオ（MP4、AVI）に変換する | Men of Letters（メン・オブ・レターズ） – 論理的思考/業務改善/プログラミング https://laboratory.kazuuu.net/convert-gif-images-to-mp4-avi-in-python-using-moviepy/

## GIFアニメをMP4に変換するコード

GIFアニメ(gifファイル)をMP4に変換するコードも作ってみました。

使用するには、事前にmoviepyというライブラリをインストールする必要があります。

```
pip install moviepy
```

### CUIバージョン

```python:
#ライブラリインポート
import moviepy.editor as mp
#gif動画ファイルの読み込み
movie_file=mp.VideoFileClip('slideshow_swipe.gif.gif')
#mp4動画ファイルの保存
movie_file.write_videofile('slideshow_swipe.gif.mp4')
movie_file.close()
```

### GUIバージョン

使い方は、GIFファイルを選択してRUNボタンを押すだけです。

MP4ファイルは、名前を付けて保存できるようになっています。

20MBほどのGIFファイルを変換したところ、1.4MBのMP4ファイルが出来上がりました。


```python:GIF-MP4.py
# coding: utf-8
# インポート
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
import os
from PIL import Image
import moviepy.editor as mp

### 関数 ###
# Browse...
def browse_func():
    # グローバル変数の宣言
    global fname
    typ = [('', '*')] 
    dir = '/home/'
    
    #複数ファイルを選択する
    #fname = filedialog.askopenfilenames(filetypes = typ, initialdir = dir) 
    
    #単数のファイルを選択する
    fname = filedialog.askopenfilename(filetypes = typ, initialdir = dir) 

# RUN
def run_func():
    global fname
    try:
         #gif動画ファイルの読み込み
         movie_file=mp.VideoFileClip(fname)
         #名前をつけて保存
         filename = filedialog.asksaveasfilename()
         #mp4動画ファイルの保存
         movie_file.write_videofile(filename)
         movie_file.close()
         
    except:
        # ステータスの更新
        statusbar["text"] = " Error!!"
                
### GUI  ###
# ウインドウの作成
root = tk.Tk()
# ステータスバー設置 
statusbar = tk.Label(root, text =  " Here we go!!",
                     bd = 1, relief = tk.SUNKEN, anchor = tk.W)
statusbar.pack(side = tk.BOTTOM, fill = tk.X)
# 実行ボタン
run_button = tk.Button(root, text = "RUN", command = run_func)
run_button.pack(pady = 10, ipadx = 20, side = tk.BOTTOM)
# ラベルフレーム
frame = ttk.Labelframe(root, text = "Select GIF File", padding = 10)
frame.pack(padx = 20, pady = 5, side = tk.BOTTOM)
# コンボボックス
extensions = [".jpg", ".jpeg", ".png", ".gif", ".bmp"]
cb = ttk.Combobox(frame, values = extensions)
cb.pack(side = tk.LEFT)
# 参照ボタン
browse_button = tk.Button(frame, text = "Browse...",
                          command = browse_func)
browse_button.pack(padx = 10, side = tk.LEFT)

# ウインドウ状態の維持
root.mainloop()
```

## 画像/動画ファイルに音楽をつけるコード

画像(gifなど)もしくは動画ファイル(MP4)に音楽(MP3)をつけるコードも作成してみました。

スライドショーとして作成したファイルをそのまま活用できます。

### 用意するもの

スライドショーにしたファイル(gif,mp4など)

音楽ファイル(mp3)

moviepyライブラリをインストール

```
pip install moviepy
```


### CUIバージョン

※CUIで作成する場合は、事前にファイルのパスを調べておいてください。

※完成ファイルは、「movie.mp4」ファイルとして書き出されます。


```python:
import moviepy.editor as mp

#音楽ファイルのパス
audio_path = r'/home/hoge/Hopping_Day.mp3'

#画像ファイル or 動画MP4ファイルのパス
image_path = r'/home/hoge/slideshow_swipe.gif'


#画像 or MP4ファイルを読み込む
clip = mp.VideoFileClip(image_path).subclip()

#音楽を付け足して保存
clip.write_videofile('movie.mp4', audio= audio_path)

#完成ファイルはブラウザなどで開く
```


### GUIバージョン

```python:
# coding: utf-8
# インポート
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
import os
from PIL import Image

#ライブラリインポート
import moviepy.editor as mp


### 関数 ###
# Browse...
def browse_func():
    # グローバル変数の宣言
    global fname
    typ = [('', '*')] 
    dir = '/home/'
    #複数ファイルを選択する
    #fname = filedialog.askopenfilenames(filetypes = typ, initialdir = dir) 
    #print(fname)
    
    #単数のファイルを選択する
    fname = filedialog.askopenfilename(filetypes = typ, initialdir = dir) 

# Browse...
def browse_func2():
    # グローバル変数の宣言
    global fname2
    typ = [('', '*')] 
    dir = '/home/'
    #複数ファイルを選択する
    #fname = filedialog.askopenfilenames(filetypes = typ, initialdir = dir) 
    #print(fname)
    
    #単数のファイルを選択する
    fname2 = filedialog.askopenfilename(filetypes = typ, initialdir = dir) 


# RUN
def run_func():
    global fname
    global fname2
    try:
         #画像 or MP4ファイルを読み込む
         clip = mp.VideoFileClip(fname2).subclip()

         #音楽を付け足して保存
         clip.write_videofile('movie.mp4', audio= fname)
         
    except:
        # ステータスの更新
        statusbar["text"] = " Error!!"
                
### GUI  ###
# ウインドウの作成
root = tk.Tk()
# ステータスバー設置 
statusbar = tk.Label(root, text =  " Here we go!!",
                     bd = 1, relief = tk.SUNKEN, anchor = tk.W)
statusbar.pack(side = tk.BOTTOM, fill = tk.X)
# 実行ボタン
run_button = tk.Button(root, text = "RUN", command = run_func)
run_button.pack(pady = 10, ipadx = 20, side = tk.BOTTOM)

# ラベルフレーム
frame = ttk.Labelframe(root, text = "Select Audio File", padding = 10)
frame.pack(padx = 20, pady = 5, side = tk.BOTTOM)
# コンボボックス
extensions = [".jpg", ".jpeg", ".png", ".gif", ".bmp"]
cb = ttk.Combobox(frame, values = extensions)
cb.pack(side = tk.LEFT)
# 参照ボタン
browse_button = tk.Button(frame, text = "Browse...",
                          command = browse_func)
browse_button.pack(padx = 10, side = tk.LEFT)


# ラベルフレーム2
frame2 = ttk.Labelframe(root, text = "Select Image/MP4 File", padding = 10)
frame2.pack(padx = 20, pady = 5, side = tk.BOTTOM)
# コンボボックス2
extensions2 = [".jpg", ".jpeg", ".png", ".gif", ".bmp"]
cb2 = ttk.Combobox(frame2, values = extensions2)
cb2.pack(side = tk.LEFT)
# 参照ボタン2
browse_button2 = tk.Button(frame2, text = "Browse...",
                          command = browse_func2)
browse_button2.pack(padx = 10, side = tk.LEFT)

# ウインドウ状態の維持
root.mainloop()

```





### 参考サイト

フォルダ内の画像からスライドショーを作る(スワイプ切り替え) - Pythonでいろいろやってみる https://tat-pytone.hatenablog.com/entry/2022/01/24/204507

今回作成したアプリは、Pythonで短い動画を作るノウハウを学ぶために試しに作ったものです。

第1弾としてスライドショー作成アプリを作ろうとググっていたら、上記のサイトを見つけ、なかなか良くできていたので、そのままGUIアプリ化してみました。

実験的に作ったものなので、公開するつもりはありませんでしたが、実際に使ってみると、非常に便利で使いやすかったので、公開することにしました。

たぶん、今まで作成したGUIアプリの中で、もっとも有用なアプリだと思います。

上記のコードを作成した方に感謝します。