渡邉一郎日記

趣味かつ独学の成人男性がPython の様々な内容を記事にして発信しております

【tkinter】PythonでYouTubeの動画をダウンロードするGUIアプリ作成

以前、Pythonライブラリのpytubeを利用してYouTubeの動画をダウンロードする方法を紹介しました。

watanabe-ichiro-nikki.hatenablog.com

上記の方法はコマンドプロンプト上で操作しており、プログラミングに慣れていない人からするととっつきにくいと感じるかもしれません。

そこで今回は、動画のダウンロード作業をGUI上で行えるようするために、tkinterを使ってGUIアプリを作成しました。

この記事では、PythonGUI構築ライブラリ「tkinter」を利用して、YouTubeの動画をダウンロードするためのGUI作成方法を説明します。

⚠動画のダウンロード元は公式チャンネルにしましょう。違法アップロードされた動画のダウンロードは違法です。注意しましょう。

ソースコード

最初に、ソースコードを下記に示します。
また、下記コードを「yt_dl_gui.py」として保存しています。

import tkinter as tk
from pytube import YouTube
import threading


#----------------------------------1枚目のフレーム----------------------------------

# 1枚目のフレーム作成
def downloader_1():
  # ウィンドウのタイトル指定
  root.title("Youtube Downloader")
  #
  # フレーム作成
  frame = tk.Frame(root)
  frame.grid(row=0, column=0, sticky=tk.NSEW)
  #
  # ウィジェット作成
  lbl1_1 = tk.Label(frame, text="URLを入力してください", font=(yu_font, 30))
  txtbox1_1 = tk.Entry(frame, font=(yu_font, 15))
  btn1_1 = tk.Button(frame, text="実行", font=(yu_font, 20), cursor=mouse,
                     command=lambda: downloader_2(txtbox1_1.get()))
  #
  # ホバー時にボタンの色を変更
  btn1_1.bind("<Enter>", enter_bg)
  btn1_1.bind("<Leave>", leave_bg)
  #
  # ウィジェット配置
  lbl1_1.place(x=180, y=150)
  txtbox1_1.place(x=100, y=300, width=450, height=35)
  btn1_1.place(x=600, y=300, width=100, height=35)
  #
  # フレームを最上面へ
  frame.tkraise()
  #

#-----------------------------------------------------------------------------------

#----------------------------------2枚目のフレーム----------------------------------

# 2枚目のフレーム作成
def downloader_2(url):
  # ウィンドウのタイトル指定
  root.title("Youtube Downloader")
  #
  # フレーム作成
  frame = tk.Frame(root)
  frame.grid(row=0, column=0, sticky=tk.NSEW)
  #
  # ウィジェット作成
  lbl2_1 = tk.Label(frame, text="動画をダウンロードしています...", font=(yu_font, 30))
  #
  # ウィジェット配置
  lbl2_1.place(x=80, y=200)
  #
  # ラベルを表示した5秒後に、3枚目へ進む
  lbl2_1.after(5000, downloader_3)
  #
  # 並列処理の命令
  thr2_1 = threading.Thread(target=frame.tkraise)
  thr2_2 = threading.Thread(target=YouTube(str(url)).streams.get_highest_resolution().download)
  #
  # フレームを最上面へ
  thr2_1.start()
  #
  # 動画のダウンロード
  thr2_2.start()
  #

#-----------------------------------------------------------------------------------

#----------------------------------3枚目のフレーム----------------------------------

# 3枚目のフレーム作成
def downloader_3():
  # ウィンドウのタイトル指定
  root.title("Youtube Downloader")
  #
  # フレーム作成
  frame = tk.Frame(root)
  frame.grid(row=0, column=0, sticky=tk.NSEW)
  #
  # ウィジェット作成
  lbl3_1 = tk.Label(frame, text="ダウンロードが完了しました", font=(yu_font, 30))
  lbl3_2 = tk.Label(frame, text="最初のページに戻ります", font=(yu_font, 15))
  #
  # ウィジェット配置
  lbl3_1.place(x=130, y=200)
  lbl3_2.place(x=280, y=350)
  #
  # 3秒後、1枚目に戻る
  lbl3_1.after(3000, downloader_1)
  #
  # フレームを最上面へ
  frame.tkraise()
  #

#-----------------------------------------------------------------------------------

#------------------------------------初期設定------------------------------------

# ウィンドウ作成
root = tk.Tk()

# ウィンドウの大きさを固定
root.minsize(width=800, height=500)
root.maxsize(width=800, height=500)

# ウィンドウ内部のエリア変化率を1:1に固定
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)

# フォント指定
yu_font = "游ゴシック"

# ボタンにカーソルが乗ったときの色指定
def enter_bg(event):
  event.widget["bg"] = "#D3D3D3"

# ボタンからカーソルが離れたときの色指定
def leave_bg(event):
  event.widget["bg"] = "SystemButtonFace"

# マウスホバー時のカーソル変更
mouse = "hand2"

#-----------------------------------------------------------------------------------

downloader_1()

root.mainloop()

GUIは、1つのウィンドウに3つのフレームを重ねて作成しました。

コードの詳細をこれから説明していきます。

ライブラリのインストール

今回作成したプログラムで使用しているPythonライブラリは、

の3つです。

この内、「threading」は標準でインストールされているため、残りの2つをインストールしていきます。

tkinter

python -m pip install tkinter

コマンドプロンプトを起動して、上記コマンドを実行してください。

Pythonライブラリのインストール方法に関しては、下記の記事が参考になると思います。

watanabe-ichiro-nikki.hatenablog.com

pytube

python -m pip install pytube

同様に、コマンドプロンプトを起動して上記コマンドを実行してください。

初期設定

GUIの基盤となる「ウィンドウ」「フォント」「カーソル」などの設定を行なっていきます。
初期設定後は、ウィンドウに重ねていく各フレームの設定を行ないます。

ウィンドウの作成

root = tk.Tk()

root.mainloop()

tkinter.Tk()」を実行することにより、初期ウィンドウが作成できます。
このウィンドウ上にこれから表示するフレームやテキスト、ボタンなどのウィジェットを作成・配置していきます。

プログラムの一番最後で「root.mainloop()」を実行することにより、
tkinter.Tk()」以降にコーディングした全ての内容をウィンドウ上に表示させ、
プログラムが終了するまで文字入力やボタン押下などに応答する機能を持たせます。

ウィンドウの大きさを固定

root.minsize(width=800, height=500)
root.maxsize(width=800, height=500)

PC上ではアプリケーションのウィンドウを「拡大」「縮小」させることが可能です。
ただ、その操作によってテキストやボタンのレイアウトが崩れてしまう恐れがあります。

そこで、拡大時・縮小時どちらも同じ大きさに設定することでレイアウトが崩れることを防いでいます。

ウィンドウ内部のエリア変化率を1:1に固定

root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)

ここでは、作成したウィンドウ内の仕切られたエリアについて設定しています。
各変数の意味は以下になります。

  • 括弧内の「0」は、0行目や0列目という意味
  • 括弧内の「weight」は、ウィンドウの大きさを変化させたときのエリア変化率

今回はウィンドウ内にgrid関数でエリアを設けていないため、行・列どちらも1つしかありません。
その行・列の変化率を「1:1」に設定しています。

フォント指定

yu_font = "游ゴシック"

ウィジェットの中でテキストを扱うものにはフォントを指定することができます。
使いたいフォントの種類をここで指定して、後に各ウィジェットへ入力します。

ボタンにカーソルが乗ったとき・離れたときの色指定

# ボタンにカーソルが乗ったときの色指定
def enter_bg(event):
  event.widget["bg"] = "#D3D3D3"

# ボタンからカーソルが離れたときの色指定
def leave_bg(event):
  event.widget["bg"] = "SystemButtonFace"

この2つの関数は、ウィジェットへホバーしたときにウィジェット内の背景色を変化させる関数です。

「enter_bg()」でホバー時の背景色(#D3D3D3:ライトグレー)
「leave_bg()」でカーソルが離れたときの背景色(SystemButtonFace:デフォルト色)

を指定しています。

マウスホバー時のカーソル変更

mouse = "hand2"

ウィジェットの作成で使用する変数の中には、そのウィジェットがホバーされたときにカーソルの形を変化させるための変数が存在します。

その変数に入力するためのカーソルの種類をここで決めています。

1枚目のフレーム

1枚目のフレーム

1枚目のフレームには、テキスト・テキスト入力欄・ボタンを配置しています。

今回のGUIで最も重要となる動画のURLを入力する画面になります。

ただ、画面の作りはシンプルで記述する内容もわかりやすいと思いますので、1つ1つしっかりと説明していきます。

ウィンドウのタイトル指定

root.title("Youtube Downloader")

「root ( =tkinter.Tk() )」とは初期ウィンドウのことです。そのウィンドウの左上に表示されるタイトルをここで指定しています。

フレーム作成

frame = tk.Frame(root)
frame.grid(row=0, column=0, sticky=tk.NSEW)

tkinter.Frame(root)」で、初期ウィンドウの中にフレームを作成しています。
そして「frame.grid()」で、ウィンドウ内でのフレームの位置と表示する範囲を指定しています。

初期ウィンドウには、エリアが1つ(1行1列)しかありません。
「row=0, column=0」はそのエリアを示しています。
「sticky=tk.NSEW」では、フレームをエリア内の上下左右に目一杯広げることを指定しています。

つまり、「フレームの大きさ=初期ウィンドウの大きさ」となります。

ちなみに「NSEW」は東西南北のイニシャルを表しています(North:北、South:南、east:東、west:西)。これで上下左右という意味になっているようです。

ウィジェット作成

# テキストを表示
lbl1_1 = tk.Label(frame, text="URLを入力してください", font=(yu_font, 30))

# 文字を入力するエリアを作成
txtbox1_1 = tk.Entry(frame, font=(yu_font, 15))

# ボタンを作成
btn1_1 = tk.Button(frame, text="実行", font=(yu_font, 20), cursor=mouse,
                   command=lambda: downloader_2(txtbox1_1.get()))

tkinter.Label()」はテキスト、「tkinter.Entry()」はテキスト入力欄、「tkinter.Button()」はボタンをそれぞれ表示させる関数です。

ウィジェットに対して記述した内容は以下の通りです。

  • Labal:フレームの中に、”URLを入力してください”というテキストを、游ゴシック体・フォントサイズ30で表示する。
  • Entry:フレームの中に、游ゴシック体・フォントサイズ15でテキストを入力できるエリアを作成する。
  • Button:フレームの中に、游ゴシック体・フォントサイズ20で”実行”と書かれたボタンを表示する。ホバーされたときにカーソルを「mouse(="hand2")」に変更する。ボタン押下時には、関数:「downloader_2()」にEntryで入力された文字列を渡して実行する。

ボタンウィジェットには、押されたときに関数を実行する機能(command)があります。
ここで指定した関数:「downloader_2()」は、2枚目のフレームへの移行を指しています。

そのため、ボタンが押された際には、フレームが1枚目→2枚目に変更される(2枚目を最上面に持ってくる)処理が行われることとなります。

また、Entry内に入力された文字列というのは動画のURLのことで、そのURLは2枚目のフレームで内部処理するため変数として渡しています。

ホバー時にボタンの色を変更

btn1_1.bind("<Enter>", enter_bg)
btn1_1.bind("<Leave>", leave_bg)

ボタンにカーソルがホバーされた時:"<Enter>"と、
ボタンからカーソルが離れた時:"<Leave>"に、
ボタンの背景色を変化させることを命令しています。

「.bind()」は、あるイベントに対して関数を紐づけることができます。

そのため、ここではボタンに対する2つのイベントそれぞれに関数を紐づけています。

ウィジェット配置

lbl1_1.place(x=180, y=150)
txtbox1_1.place(x=100, y=300, width=450, height=35)
btn1_1.place(x=600, y=300, width=100, height=35)

作成したウィジェットをフレーム内のどこに配置するかを指定しています。

内容は以下の通りです。

  • フレームの大きさ:幅800x高さ500
  • テキストを、左から180・上から150の場所に配置する
  • テキスト入力欄を、左から100・上から300の場所に、幅450x高さ35の大きさで配置する
  • ボタンを、左から600・上から300の場所に、幅100x高さ35の大きさで配置する

フレームを最上面へ

frame.tkraise()

作成したフレームを、ウィンドウの一番上に表示させるコードです。

フレームを複数枚作成する際にはこの命令をしないと、作成はされていても表示されない、ということが起こるため忘れないようにしましょう。

2枚目のフレーム

2枚目のフレーム

2枚目以降には1枚目と似た内容の記述がいくつかあるため、それらは説明から省きます。

2枚目のフレームでは、1枚目で入力されたURLを使って動画をダウンロードします。
また、動画のダウンロードが終わった後に3枚目のフレームに移行するための命令も必要になります。

よって、2枚目のフレームは画面上ではシンプルですが、内部処理が色々実行されるフレームになります。

ラベルを表示した5秒後に、3枚目へ進む

lbl2_1.after(5000, downloader_3)

「lbl2_1」とは、テキスト:”動画をダウンロードしています...”のことです。

上記の命令の意味は、テキストを表示した5秒後に関数:「downloader_3」(3枚目のフレームへ移行する関数)を実行する、となります。

ここで、指定する時間の単位はミリ秒(ms)という条件があります。
よって、「5000x1/1000x1s=5s」となることから「5000」を入力しています。

並列処理の命令・動画のダウンロード

# 並列処理の命令
thr2_1 = threading.Thread(target=frame.tkraise)
thr2_2 = threading.Thread(target=YouTube(str(url)).streams.get_highest_resolution().download)


# フレームを最上面へ
thr2_1.start()

# 動画のダウンロード
thr2_2.start()

threadingを使用することにより、プログラム内で並列処理を行うことが可能になります。

通常、プログラムは上から順に処理が行われます。
よって、ある処理が終わらない限りその下の処理は実行されない、ということが起こります。

それを解決する方法として「threading」が使われます。

今回は、
「①2枚目のフレームを最上面に持ってくること」と「②動画のダウンロード」
の2つを並列処理させています。

並列処理を使わなくてもフレームは移行しますが、少しタイムラグが起こっていました。

結局、並列処理を行なってもちょっとだけラグが残ってしまいましたが、こちらの方がスムーズにフレームの移行が行われていたので採用しました。

3枚目のフレーム

3枚目のフレーム

いよいよ最後のフレームです。

3枚目のフレームを表示する頃には、動画のダウンロードも終わり、ユーザーも特に操作することはありません。
よって、3枚目では動画のダウンロードが完了した旨をテキストで表示させてから、1枚目のフレームへ自動で戻る機能を持たせます。

3秒後、1枚目に戻る

lbl3_1.after(3000, downloader_1)

「lbl3_1」とは、テキスト:”ダウンロードが完了しました”のことです。

「.after」は2枚目のフレームでも説明した通り、任意の処理が行われてから〇〇ミリ秒後にある関数を実行する機能です。

ここでは、テキストを表示した3秒後に関数:「downloader_1」(1枚目のフレームへ移行する関数)を実行する、という意味の命令になります。

以上で、GUIの初期設定・画面構成・内部処理などの説明は終わりになります。

実行結果

それでは作成したGUIを起動させて、YouTubeの動画のURLを入力またはペーストし、動画をダウンロードしていきます。

正常時の動作

正常時の動作

URLを入力して実行ボタンを押すと、フレームが1枚目→2枚目に移行して動画が保存されます。
その後、フレームが2枚目→3枚目に移行して3秒後に1枚目のフレームに戻ってきました。

ダウンロードした動画の保存先は、Pythonファイルが置かれているフォルダになります。

上の画像のように、Pythonファイルをコマンドプロンプトから実行する方法は下記の記事を参考にしてください。

watanabe-ichiro-nikki.hatenablog.com

エラー時の動作

エラー時の動作

これは、URLを入力せずに実行ボタンを押した際の挙動になります。

URLが入力されていないので、内部処理ではエラーが発生しています。
ただ、GUI上ではフレームの移行が正常時と同じように実行されていました。

今回のプログラムでは、内部処理でエラーが発生した際に、GUI上にもその内容を伝えるテキストを表示させるような命令は記述していませんでした。

よって、エラー発生時の動作は、何もダウンロードされないままフレーム移行だけ行われる、ということになります。

まとめ

この記事では、PythonGUI構築ライブラリ「tkinter」を利用して、YouTubeの動画をダウンロードするためのGUI作成方法を説明しました。

今後、時間がかかってしまうかもしれませんが、エラー発生時の内部処理とフレームの挙動をリンクさせるコードを追記できればなと思っています。

また、tkinterの基礎の部分を説明した記事も書きたいと思っておりますので、その時はこの記事にもリンクを貼ります。

ありがとうございました。

追記:
tkinterについて記事にしました。

watanabe-ichiro-nikki.hatenablog.com