見出し画像

【PythonでGUIを作りたい】Tkinterで作る画像編集ソフト#11:gif保存の機能追加とリファクタリング(前編)

こんにちは。すうちです。

昨年『Tkinterで作る画像編集ソフト』というお題でnoteに連載してました。

一通り連載が終わる頃に本当はもう少しやりたいこともありましたが、急に忙しくなって途中で止めてた所があります。

また日が経つと(自分で書いたコードではありますが…笑)なんでこんな書き方しているんだろう、とツッコミたくなることも出てきて、一度コードをリファクタリングしましたが、その時も全部は手が出せてませんでした。

というわけで、今回は以前作った画像編集ソフトに機能追加とリファクタリングした話(gif保存機能の追加とコード整理)です。

※書き始めたら結構長くなったので、前編・後編に分けて投稿します。



◆はじめに

作りたい機能のイメージ

機能追加でやりたいことメモです。

・動画編集の保存でmp4とgif選択可
・gifは保存設定も選択可(画像サイズ、スキップ数)

また上記に関連して、以下も見直したいと思います。

・動画保存の処理スレッド化(並列処理)
・動画保存時の進捗状況表示

このメモを元に、仕様検討と実装を進めます。


◆仕様検討(ユーザ視点)

gif保存の設定

きっかけは noteのgif容量制限
noteは本文に動画(mp4等)を直接アップロード不可ですがgifは可能です。

gifとは複数の画像からアニメーションを作れるフォーマットでサポート環境も多く自動再生してくれます。ただ、一般的な動画に比べ容量が大きい特徴があります。

ちなみに、noteのgifは1画像あたり5MBが制限だそうです。

私もたまにnoteにgifをアップしますが、容量制限で大抵何度か作り直してました。

もう少し簡単にやりたいと考えて「前述の動画編集ソフトに機能追加するとよいのでは?」と思ったのが経緯です(補足:gif編集できるフリーソフトもあります…)。


gifファイルの容量を減らすには

gifといっても構成は動画と変わらず複数の静止画の集まりです。ファイルの容量を減らすには、基本的に以下2つだと思います。

・フレーム数(静止画枚数)を減らす
・画像サイズ(縦横)を小さくする

フレーム数の削減は、gifにする範囲(時間)を短くする事とフレームを間引く方法が考えられます。前者はユーザが切り出し時に指定。後者は設定値が決まっていれば保存時に処理できます。

また画像サイズ縮小も設定がわかれば同様に可能です。フレーム数が同じ場合、画像サイズが小さいほどファイル容量は小さくなります。

今回は、以下選択できるようにしたいと思います。

・画像サイズ(縦横): 1/1、1/2、1/4
・フレーム間引き:   1/1、1/4、1/8
 ※1/1は変更なし


動画保存のスレッド化と経過表示

今までは動画保存すると終わるまで待ちでした(固まって見える)。理由は保存処理をスレッド化(GUIと並列処理)してなかったからです。

当時追加で作りこむ余裕がなかったのと、処理中にログが出てるから良いかと一旦区切りをつけましたが、ずっと気になってました。。。

また、動画の保存時間(フレーム数)が短い場合は余り気になりませんが、長いと固まり状態が目につきます。ユーザには不便なので(本来最初から対策すべき自身の怠慢です…汗)動画保存も再生処理と同様にスレッド化したいと思います。

他、ユーザにも分かるようにGUI側にも動画保存の経過を表示します。


保存途中でやっぱキャンセルしたい

一旦、動画保存したものの思ったより保存に時間かかったり、編集やり直したい時にすぐキャンセルしたいと思っても、今までは前述の経緯で保存終了までGUI操作ができませんでした。

動画保存のスレッド化とあわせて、途中キャンセルの実装も追加します。


上記をふまえて GUIはどうするか?

以下のレイアウトにしました。

GUIレイアウト(修正後)

動画保存時にgif又はmp4を選択。gif選択時は画像サイズ[H,W]間引き[FPS]選択を表示(mp4では非表示)。これらは動画選択(Videoタブ)時のみ必要なので静止画選択(Photoタブ)では非表示にします。


◆設計と実装(概要)

Videoタグとgif選択の表示切替

TkinterでGUI部品(Widget)の表示切替え方法はいくつかあるようですが、今回は以下で実装しました。

選択状態で切替えたい部品をウインドウ(Tkinterフレーム)に定義して、placeとforgetを使って表示/非表示を実現しました。

図1.新規ウィンドウ(Tkinterフレーム)と部品配置

Window(フレーム)表示: WindowSub.place()
Window(フレーム)非表示:WindowSub.forget()

Photoタブ選択時は Windowサブ5、6forget()。Videoタブ選択時はWindowサブ5place()、更にgif選択時はWindowサブ6place()です。※placeは配置フレーム内のxy座標を指定


gif保存時の処理(画像縮小、間引き)

単純ですが、GUIの設定値を動画保存(関数)に渡して処理しました。

画像縮小[H,W]:Pillowのリサイズ使用
1/M (M=1, 2, 4)
new_image = pil_image.resize((w//M, h//M), resample=Image.BICUBIC)

間引き[FPS]:設定値により保存フレーム選択
1/N (N=1, 4, 8)
N=4:4フレームに1回、N=8:8フレームに1回 フレーム保存


動画保存時の並列処理(スレッド化)

大まかには複数フレームの保存処理を1フレーム単位に分割して、定期的にスレッドで処理するようにしました。

図2.従来の動画保存処理


図3.修正した動画保存処理


状態管理の修正(保存中断イベント)

従来は編集状態から動画保存で停止状態に移行(実際は処理完了まで待ち)しましたが、保存処理の並列化により動画保存中の操作をどう扱うか?状態管理が必要になります。

今回は停止(STOP)/編集状態(EDIT_XXX)から動画保存中(SAVING)の状態を追加しました。動画保存中に実行可能なイベントは保存中断(Drop)のみとしました。

Video状態管理の修正

また保存完了時の状態更新は、動画再生と同様にコールバック関数内(保存対象のフレームがない場合)で停止状態に移行としました。


◆動画保存の簡易テスト

以下、簡単な動作テストです。

Videoタグとgif選択切替(GUI表示)

PhotoタブからVideoタブ、mp4からgif選択。その後Photoタブ選択。

タブ、ファイル形式選択による表示切替


gif保存(縦横1/1、間引き1/8)

まずは、保存の様子をキャプチャした動画をgifにしています(5MBの制限のため、保存時の進捗が40%程度で切っています)。

gif保存(縦横1/2、間引き1/8)

直前に保存したgifを再生したものです。

ちなみに、noteのgif容量は厳密には5MB以下ではなさそうです。
※上記 gifは5MBこえてますがアップできました。もしかして仕様が緩和されたのでしょうか…


◆次回予定

後編は、実装の詳細(コード整理)とテスト結果を投稿予定です。

最後まで読んで頂き、ありがとうございました。



ソフトウェアを作る過程を体系的に学びたい方向けの本を書きました(連載記事に大幅に加筆・修正)

Kindle Unlimited 会員の方は無料で読めます。


#連載記事の一覧





いいなと思ったら応援しよう!

この記事が参加している募集