t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

Pythonでルーチン読み上げツールを作ってみた

最近Pythonをかじりだした理由として、Rapsberry Piで使う目的で日常ルーチンを読み上げてくれるツールを作ったことがきっかけ。

※読み上げといってもテキスト合成ではなくて、単にwavファイルに入れておいた音声を再生するだけです。
 タイトルでがっかりさせちゃってたらすみません。

イメージとしてはこんな感じで起動してきて、再生ボタンをタップするたびに次のタスクを読み上げてくれるルーチンツール。
f:id:t-hom:20190916194425p:plain

掃除って単純なようで段取りを間違うと面倒なことになる。
そもそも普段から完璧に綺麗なら何も問題ないんだけど、物が散らかり始めると、Aを掃除中にBが気になり、Bに手を付けたら今度またAが気になりという風にデッドロックがかかる。

ので、ルーチンを設計して、それに命じられるままに掃除しようと。
コンピュータに使われる私。

まずはPythonをまじめに学習する前の私が書いたコード。思考錯誤して、なんとか動いたけど、謙遜抜きでまぁひどいコードである。
よく分からないままコピペした部分も少なからずあるので。

import os
import sys
import tkinter
import playsound
from tkinter import *
from tkinter import ttk
import tkinter.font as font

base = os.path.dirname(os.path.abspath(__file__))

def play(filenumber):
    global var
    txt = open(base + "/wav/CleanupRoutine-" + str(filenumber) + ".txt", 'r', encoding='utf-8')
    var.set(txt.read())
    root.update()
    txt.close

    filename=base + "/wav/CleanupRoutine-" + str(filenumber) + ".wav"
    playsound.playAudio(filename)

root = tkinter.Tk()
root.title("Tkinter test")
root.attributes("-fullscreen", True)
#root.geometry("800x480")

frame = tkinter.Frame(root)
frame.pack(fill="x")
button = tkinter.Button(frame, text="Quit",bg="#00a4e4",fg="#ffffff", width=5, command=root.quit)
button.pack(side="right")

n = 0
def button2_clicked(event):
    canvas.itemconfig(imagearea, image = icon2)
    root.update()
    global n
    play(n)
    n = n + 1
    if n > 14 :
        n = 0

    canvas.itemconfig(imagearea, image = icon1)
    root.update()

icon1 = PhotoImage(file=base + '/next.png')
icon2 = PhotoImage(file=base + '/next_disabled.png')

my_font = font.Font(root,family="fonts-vlgothic",size=20,weight="bold")
var = tkinter.StringVar()
lbl = tkinter.Label(root, textvariable=var, height=5, font=my_font)
lbl.pack()

canvas = tkinter.Canvas(root, width=128, height=128)
imagearea = canvas.create_image(0, 0, image=icon1, anchor=tkinter.NW)
canvas.bind("<Button-1>", button2_clicked)
canvas.pack()


root.mainloop()

色々インポートしてるけど、このうちplaysoundは私が別ファイルに書いたサウンドファイル再生用のモジュールで、それ以外は標準ライブラリ的なもの。(用語もまだあまり分かってない。)

こちらが、「やさしいPython」の「クラス」の学習まで終わってから修正したコード。

import tkinter as tk
import tkinter.font as font
import os
import playsound

class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.pack()
        self.load_resources()
        self.create_widgets()
        self.n = 0

    def load_resources(self):
        self.base = os.path.dirname(os.path.abspath(__file__))
        self.icon1 = tk.PhotoImage(file=self.base + '/next.png')
        self.icon2 = tk.PhotoImage(file=self.base + '/next_disabled.png')

    def create_widgets(self):
        #Root Setting
        self.master.title("Cleanup Routine")
        #self.master.attributes("-fullscreen", True)
        self.master.geometry("800x480")

        #Generate Parts
        frame1 = tk.Frame(master = self.master)

        quit_button = tk.Button(
            master = frame1,
            command = self.master.destroy,
            width = 5,
            text = "Quit",
            bg = "#00a4e4",
            fg = "#ffffff")

        next_button = tk.Canvas(
            master = self.master,
            width = 128,
            height = 128)
        self.imagearea = next_button.create_image(
                0, 0, image=self.icon1, anchor=tk.NW)
        next_button.bind("<Button-1>", self.next_clicked)

        self.var = tk.StringVar()
        my_font = font.Font(root,family="Migu 1M",size=20,weight="normal")
        lbl = tk.Label(root, textvariable=self.var, height=5, font=my_font)
        #Layout
        frame1.pack(fill="x")
        quit_button.pack(side="right")
        lbl.pack()
        next_button.pack()

        #Attributation
        self.frame1 = frame1
        self.quit_button = quit_button
        self.next_button = next_button
        self.lbl = lbl

    def play(self, filenumber):
        txt = open(self.base + "/wav/CleanupRoutine-" + str(filenumber) + ".txt", 'r', encoding='utf-8')
        self.var.set(txt.read())
        root.update()
        txt.close
    
        filename=self.base + "/wav/CleanupRoutine-" + str(filenumber) + ".wav"
        playsound.playAudio(filename)

    def next_clicked(self, event):
        self.next_button.itemconfig(self.imagearea, image = self.icon2)
        self.master.update()
        n = self.n
        self.play(n)
        n = n + 1
        if n > 14:
            n = 0
        self.n = n
        self.next_button.itemconfig(self.imagearea, image = self.icon1)
        self.master.update()


root = tk.Tk()
app = Application(master=root)
app.mainloop()

学習によって理解度も進んだので、最初のコードより幾分マシに見える。
インポート文の仕組みも分かったので余計なものをインポートするのもやめられた。

GUIは、Tkinterのサンプルコードをベースに必要なGuiパーツを肉付けしていくというやり方で作った。
実は最初のコードを書く段階ではTkinterのサンプルでクラスが使われているのでよく理解できず、クラスを使ってないサンプルを探し回って使ったんだけど、きちんと学習した後はクラスを使った方が合理的だと分かって書き直した。

ただ今度はクラスの肥大化とサウンド再生とかテキスト読み込み・出力とかがアプリケーションクラスにごちゃっと入ってるので、それはそれでメンテナンス性悪いのではという感じ。

selfの使い方も分かったけど、なんでもselfつけてとりあえずインスタンスの属性に放り込んでおけばいいや的な使い方をしてるので、「合ってるのかこの使い方?」という疑問がある。

次の課題はModel View Controllerの分離。
qiita.com

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。