Tkinterを使ってフォトフレームカレンダーを作ってみた

# はじめに
「その日によって流す画像を切り替えたい」
「推しの誕生日にはお祝いスライドショーを流したい」
「家族や恋人と、何日にどのように過ごしたか、スライドショーで振り返りたい」

これらを一度に実現できるフォトフレームカレンダーって、意外と見られないんですよね。
ということで、作っちゃいました。

使用言語はPython、Raspberry Pi上で動作させ、7インチディスプレイに出力させることを想定してます。

Pythonを使ったフォトフレームもしくはカレンダーの作り方は数多く公開されてますので、それらの技術をTkinter上で複合させてみました。

# 実装した機能
- カレンダーの日付が押しボタンになっている
    - クリックするとその日の六曜、旧暦、祝日名、設定したイベント名を確認できる
- カレンダーの表示年月を自由に変更できる
- フォトフレームで、日毎に設定したイベントの写真を自動で切り替えられる
    - イベントが複数重なってたら全部流す
    - イベントを設定していない日には、月毎に共通で設定した画像を流す

折角Raspberry Pi上で動かすので、以下をおまけで追加
- アナログ時計
- 安眠用の渓流音を設定した時間帯に流す

# 使い方

https://github.com/ScarletAK/TkinterPhotoFrameCalendar

## インストール
1.動作させるPC上に上記リンクのリポジトリをクローン
2.動作に必要なライブラリをインストール
- Pillow
- pyaudio
- pyautogui
- schedule
- qreki（旧暦の出力、fgshun氏の[qreki_py](https://github.com/fgshun/qreki_py)を利用）

## 設定
settingsフォルダ内に配置しているConfigure.json及びEventDB.csvの内容を動作環境に応じて変更する。
```Configure.json
{
  "SlideShow": {
    "Interval": 10,
    "BaseFolder": "sampleimages",
    "MonthComm": {
      "1": "01_Common_Jan",
      "2": "02_Common_Feb",
      "3": "03_Common_Mar",
      "4": "04_Common_Apr",
      "5": "05_Common_May",
      "6": "06_Common_Jun",
      "7": "07_Common_Jul",
      "8": "08_Common_Aug",
      "9": "09_Common_Sep",
      "10": "10_Common_Oct",
      "11": "11_Common_Nov",
      "12": "12_Common_Dec"
    }
  },
  "StreamSound": {
    "FileName": "stream.wav",
    "Start": "00:00",
    "End": "03:00"
  }
}
```
- "Slideshow"：スライドショーの設定
    - "Interval"：画像の切り替え間隔（単位：秒）
    - "BaseFolder"：画像を格納するルートフォルダのパス
    - "MonthComm"：月毎共通の画像格納フォルダ名（イベントを設定していない日に表示させる画像を格納、<ins>"BaseFolder"内に配置することが前提</ins>）

画像の切り替え間隔を5秒、Raspberry Piに接続したUSBメモリ（KIOXIA製）から画像を呼び出す場合の設定は以下の通り。
```
{
  "SlideShow": {
    "Interval": 5,
    "BaseFolder": "/media/XXX/KIOXIA/",
    (以下省略)
}
```
- "StreamSound"：渓流音の設定
    - "FileName"：出力させる音声ファイル名（soundsフォルダ内に格納されているファイル名と一致させること）
    - "Start"：出力開始時刻
    - "End"：出力停止時刻

[EventDB.csv](https://github.com/ScarletAK/TkinterPhotoFrameCalendar/blob/main/TkinterPhotoFrameCalendar/settings/EventsDB.csv)には、下表のようにイベントの月日と名称、スライドショーに流す画像の格納フォルダ名を記入する。なお、格納フォルダはConfigure.jsonの<ins>"BaseFolder"に記入したフォルダへ配置する</ins>ことが前提。
格納フォルダ名を記載しない場合、日付詳細画面にイベント名が表示されるのみ。
<table>
  <caption>EventDB.csvの構成</caption>
  <thead>
    <tr>
      <th>Month</th> <th>Day</th> <th>Name</th> <th>ImageFolder</th>
    </tr>
  </thead>
  <tr>
    <td> 月 </td> <td> 日 </td> <td> イベント名 </td> <td> (スライドショーを流す場合のみ)画像格納フォルダ名 </td>
  </tr>
</table>

## プログラム起動
上記のインストール及び設定を行った後、pythonコマンドでmain.pyを実行する。
```
python3 main.py
```
以下の画面が表示される。
![TkinterPhotoFrameCalendar起動時画面表示.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498181/a902a840-aad6-4d59-b40c-e819d23634ee.png)
起動直後は上記のように滝の画像（top_sample.jpg）が表示されるが、しばらくするとスライドショーに流す画像が表示される。

![レコーディング 2025-05-14 235657.mp4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498181/0d5d142c-914c-4b9d-9be0-46b2f221c507.gif)
※流れている画像はsampleimages\05_Common_Mayフォルダに格納されているもの

なお、EventDB.csvに設定したイベントの日付当日には、「ImageFolder」に指定したフォルダの画像が流れる。
（同じ日に複数のイベントを設定している場合、全てのフォルダの画像を流す。）

```EventDB.csv
Month,Day,Name,ImageFolder
 : 
5,17,テストイベント1,Test_Event_1
5,17,テストイベント2,Test_Event_2
```

![レコーディング 2025-05-17 143003.mp4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498181/939f98de-add3-4ed6-9e6a-7ea698c9dc88.gif)

Test_Event_1(左)とTest_Event_2(右)の中身
![Test_Event_12.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498181/d16ebdc7-82db-452d-86c9-18ed6355aa49.jpeg)

# 画面毎の機能説明

## フォトフレームカレンダー画面（初期画面）
![TkinterPhotoFrameCalendar起動時画面表示_説明付き.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498181/019fc36b-cffd-4d92-9310-4dc8bddfabf4.png)
①：今月のカレンダー表示に戻すボタン
②：前月／翌月のカレンダーを表示するボタン
③：表示されているカレンダーの年月を表示及び選択。1900年～2100年まで選択できる
④：カレンダー。日付はボタンとなっており、押下することで下記の詳細画面に遷移する。今日のボタンは背景色が変わる
⑤：ディジタル時計
⑥：フォトフレーム。月毎及びEventDB.csvで設定した日によって流す画像を変えられる

カレンダーの操作例
![レコーディング 2025-05-15 195920.mp4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498181/6af09665-a4dd-4bd5-b788-3f1ace532719.gif)

日付詳細画面へ遷移する様子
![レコーディング 2025-05-14 235816.mp4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498181/40ea9d35-120e-4aa0-89a3-3395c55b073b.gif)

## 日付詳細画面
![TkinterPhotoFrameCalendar日付詳細画面_説明付きpng.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498181/79b768b1-33eb-4945-988e-99375c44df75.png)
①：カレンダーで選択した日付の詳細を表示。祝日名、干支、和暦、旧暦、六曜、二十四節季を表示する。EventDB.csvにイベントを設定している場合、イベント名を表示する（同日に複数のイベントを設定している場合、全て表示）
②：前日／翌日の詳細を表示するボタン
③：アナログ時計。針は<ins>外側から内側に向いている</ins>（元ネタは、ゲーム テイルズオブエクシリア２の主人公ルドガーの懐中時計）

アナログ時計の文字盤（clock_oval.jpg）は、[リンク](https://illustimage.com/?id=11732 "ローマ数字の時計文字盤イラストのフリー素材｜イラストイメージ")のフリー素材を加工して使用。

## メニューバー
全画面で共通
- Window 
![スクリーンショット 2025-05-09 220029.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498181/3ac92e57-db4c-4390-837a-5cc708d3b59d.png)
    - Calendar：フォトフレームカレンダー画面へ遷移
    - DayDetail：日付詳細画面に遷移

- Close
![スクリーンショット 2025-05-09 220106.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498181/771137b4-8630-4bdd-b5b1-40bf394197f5.png)
    - Screen Close：プログラムを停止
:::note warn
プログラムは必ず「Screen Close」で停止すること。
:::

# 安眠用渓流音
直接関係ない機能だが、Raspberry Piで動かすならついでに安眠用として渓流音流そうと思い追加。
Configure.jsonで音の種類及び再生時刻を自由に設定できるようにした。
デフォルトでは[効果音ラボ](https://soundeffect-lab.info/sound/environment/ "環境音[1]｜効果音ラボ")のフリー素材「渓流」をwavファイルに変換して使用。

:::note warn
別の音声ファイルを使用する場合、必ずWAVファイルを使用すること。
:::

# プロジェクト内フォルダ構成
```
TkinterPhotoFrameCalendar
 ├ AppCodes
 |  ├ ApplicationMain.py
 |  ├ BaseLibrary.py
 |  ├ CalendarWindow.py
 |  ├ Configuration.py
 |  ├ DayDetailWindow.py
 |  ├ ImportCommon.py
 |  └ OutputMedia.py
 ├ images
 |  ├ AnalogClock
 |  |  └ clock_oval.jpg
 |  ├ Slideshow
 |  |　　※スライドショー表示画像を保存。プログラム起動時に自動生成され、停止時に削除される
 |  └ top_sample.jpg
 ├ settings
 |  ├ Configure.json
 |  └ EventDB.csv
 ├ sounds
 |  └ stream.wav
 └ main.py
```

# スライドショー表示画像
テストイベント及び月毎共通画像のサンプルをリポジトリ内に保存している。
初期画像（top_sample.jpg）も含めて、Grokで生成。
```
TkinterPhotoFrameCalendar
 ├ TkinterPhotoFrameCalendar
 └ sampleimages
  　├ 01_Common_Jan
  　├ 02_Common_Feb
  　├ 03_Common_Mar
  　├ 04_Common_Apr
  　├ 05_Common_May
  　├ 06_Common_Jun
  　├ 07_Common_Jul
  　├ 08_Common_Aug
  　├ 09_Common_Sep
  　├ 10_Common_Oct
　  ├ 11_Common_Nov
  　├ 12_Common_Dec
　  ├ Test_Event_1
  　├ Test_Event_2
　  └ Test_Event_3
```

# 参考URL

https://memopy.hatenadiary.jp/entry/2017/06/17/204107

https://www.asahi-net.or.jp/~rn8t-nkmr/family/pic/pf/index.html

https://mulberrytassel.com/python-practice-tkinter-6/

https://youta-blog.com/tkinter-analog-clock/