【Where is 需要】Pythonで作業用音楽耐久アプリ開発  前編

# 目的
僕は、作業をしている時、音楽を耐久でかけるのですが、音楽プレイヤーがどうも使いにくい。耐久は主に1曲しかできず、何曲も聞きたい私はプレイリストを作成するが、それだと耐久ができない。いちいち音楽プレイヤーのウィンドウを開いて曲を変えるのも面倒。
**何曲も再生できる上に耐久時間を設定することができるという夢のアプリが欲しい**。そんな貪欲な欲望を満たすためにアプリを開発します。(多分ネットに転がっているけどせっかくなら自分で作る。)

# 必要なもの
・pythonの実行環境
・tkinterの基礎知識
・**絶対に作るというやる気とバグに負けない根気**

# ソースコード
今回使うライブラリはos、tkinter、pygameです。頑張れば100行に収まりそうだけど、長くなっちゃいました。**うんこーど**です。
```python:誰得
import os
import tkinter as tk
from tkinter import filedialog
from tkinter import ttk
import pygame
# 初期化
pygame.init()
mixer = pygame.mixer
mixer.init()
# 各種変数、フラグ、リストの設定
playlist = []
selected_song = 0
is_playing = False
current_m3u_file = None
start_song = False
durability = 30
font = ("Helvetica", "12")
# tkinterに関わる処理
root = tk.Tk()
root.title("誰得プレイヤー")
root.geometry("540x300")
root.iconphoto(True, tk.PhotoImage(file="icon.png"))
playlist_box = tk.Listbox(root, font=font, selectbackground="gray", selectmode=tk.SINGLE, width=30, height=10)
playlist_box.pack(padx=20, pady=10)
value = tk.StringVar()
text_box = tk.Entry(root)
text_box.insert(0, 30)
text_box.pack()
# プレイリストに曲を追加する関数
def add_songs_to_playlist():
    root = tk.Tk()
    root.withdraw()
    file_paths = filedialog.askopenfilenames(filetypes=[("MP3 files", "*.mp3")])
    playlist.extend(file_paths)
    update_playlist_box()
# m3uファイルからパスをロードする関数
def load_playlist_from_file():
    file_path = filedialog.askopenfilename(filetypes=[("M3U files", "*.m3u")])
    if file_path:
        load_playlist_from_m3u(file_path)
        current_m3u_file = file_path
        update_playlist_box()
# プレイリストを更新する関数
def update_playlist_box():
    playlist_box.delete(0, tk.END)
    for song in playlist:
        playlist_box.insert(tk.END, os.path.splitext(os.path.basename(song))[0])
# プレイリストをm3uファイルに保存する関数
def save_playlist_to_m3u(filename=None):
    global current_m3u_file
    
    if not filename:
        filename = filedialog.asksaveasfilename(defaultextension=".m3u", filetypes=[("M3U files", "*.m3u")])
    
    if filename:
        with open(filename, 'w', encoding="utf-8") as m3u_file:
            for song in playlist:
                m3u_file.write(song + '\n')
            current_m3u_file = filename
# m3uファイルから曲をロードする関数
def load_playlist_from_m3u(filename):
    with open(filename, 'r', encoding="utf-8") as m3u_file:
        playlist.clear()
        for line in m3u_file:
            song = line.strip()
            playlist.append(song)
        update_playlist_box()
# 曲を再生する関数
def play_song(song_index):
    mixer.music.load(playlist[song_index])
    mixer.music.play(-1)
    root.after(durability * 60000, play_next_song)
    global is_playing
    is_playing = True
# 次の曲を再生する関数
def play_next_song():
    global selected_song
    selected_song = (selected_song + 1) % len(playlist)
    play_song(selected_song)
    root.after(durability * 60000, play_next_song)
# 曲の一時停止、再会する関数
def button_play_pause():
    global is_playing,start_song
    if is_playing:
        mixer.music.pause()
        is_playing = False
    else:
        if start_song == True:
            mixer.music.unpause()
        else:
            play_song(selected_song)
            start_song = True
        is_playing = True
# 耐久時間を設定する関数
def store_value():
    global durability
    durability = int(text_box.get())
    if durability == 0:
        durability = 1
    value.set(durability)
    root.after_cancel(play_next_song)
    root.after(durability * 60000, play_next_song)
# ボタンを作成
button_frame = ttk.Frame(root)
button_frame.pack(pady=20)

play_pause_button = ttk.Button(button_frame, text="再生/一時停止", command=button_play_pause)
play_pause_button.pack(side=tk.LEFT, padx=10)

add_button = ttk.Button(button_frame, text="曲の追加", command=add_songs_to_playlist)
add_button.pack(side=tk.LEFT, padx=10)

save_button = ttk.Button(button_frame, text="プレイリストの保存", command=save_playlist_to_m3u)
save_button.pack(side=tk.LEFT, padx=10)

load_button = ttk.Button(button_frame, text="プレイリストの読込", command=load_playlist_from_file)
load_button.pack(side=tk.LEFT, padx=10)

store_button = ttk.Button(button_frame, text="適用", command=store_value)
store_button.pack(side=tk.LEFT,padx=10)
# デフォルトの耐久時間分計る
root.after(durability * 60000, play_next_song)
# 全ての処理を終了する関数
def on_closing():
    pygame.quit()
    root.destroy()
# もしquitボタンが押されたらon_closing()を呼び出す
root.protocol("WM_DELETE_WINDOW", on_closing)
# 画面を表示
root.mainloop()
```
ちなみにicon.pngはコレ☟です。
![icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3367669/01fe9d43-2295-e259-1c93-86e742e6b95a.png)
pixnote https://pixnote.net/ というブラウザツールでちょちょっと描きました。漢字なら10分もかかりませんのでオススメです。
さて、完成したものがこちらになります。
![スクリーンショット (31).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3367669/92ac8929-bbde-571d-c925-e225f6a29d28.png)
うーん...なんとかアプリとしての体裁を保ってはいるが、物足りない感がありますね...

# 関数変数キーワードの解説
・`durability`は耐久時間(分単位)です。
・`root.iconphoto(True, tk.PhotoImage(file="icon.png"))`この部分がアイコンを作る関数です。アイコンがあるとなんだがテンションが上がりますね。
・`tk.withdraw()`はrootを非表示にする関数です。これがあると無いとでアプリとしてのクオリティがダンチです。見せたくないものは見せない。
・`mixer.music.play(-1)`: 引数を-1にすると無限ループができます。
・`root.after(durability * 60000, play_next_song)`は耐久時間(分)×60000ミリ秒分だけ待ち、play_next_song()を呼び出します。

## 全体の構造(時系列順)
1.ユーザーがアプリを起動する
2.画面が表示される
3.読込ボタンが押される
4.ユーザーがファイルから選ぶ
ex.プレイリストを保存する
ex.曲の追加
5.再生ボタンを押す
6.曲が流れる
7.dulability*60000経ったら次の曲へ
8.最後の曲が終わったら最初の曲へ
9.quitボタンが押される
10.アプリを終了する
とまあ、こんな感じです。

# 感想
疲れた...飽きた...
まだまだバグばかりでデザインもUIもカスですが一応クソアプリとしての体裁は保てていると思います。
来年の9月の文化祭で頒布しようかな...それまでになんとかメディアプレーヤーと肩を並べられるくらいの完成度を目指します...
まあ、前編だもんね！まだ後編があるし中編を挟んだってええんや！
足りないとこあったらご意見批判文句ブーイング何でも受け付けます。