pythontkintercustomtkinterpytube

How to fix my CustomTkinter YT downloader .exe file


I made a small project using CustomTkinter and Pytube to download YouTube videos with a simple GUI.

The program has zero errors when run as a .py file in the IDE and Windows if I double-click or use the terminal.

I then proceeded to use auto-py-to-exe to convert the file into a .exe file. I followed the instructions on the CustomTkinter website and although I had some trouble I managed to get through the process.

The final .exe file, however, does run but only for a moment before closing itself again. By running the .exe from a cmd terminal based in the directory of the file I was able to get an error message:

Traceback (most recent call last):
File "PyInstaller\hooks\rthooks\pyi_rth_pkgres.py", line 158, in <module>
File "PyInstaller\hooks\rthooks\pyi_rth_pkgres.py", line 36, in _pyi_rthook
File "PyInstaller\loader\pyimod02_importers.py", line 419, in exec_module
File "pkg_resources\__init__.py", line 77, in <module>
ModuleNotFoundError: No module named 'pkg_resources.extern'
[55288] Failed to execute script 'pyi_rth_pkgres' due to unhandled exception!

I was unable to find a solution to this and so I have ended up here.

Do you know what could be causing this error? or if indeed this is the reason the program does not properly function?

Full code:

import tkinter
import customtkinter
#from pytubefix import YouTube
from tkinter import filedialog
from moviepy.editor import *
from pathvalidate import sanitize_filename
#from pytubefix import Playlist
#from pytubefix.cli import on_progress\
from pytube import *


app = customtkinter.CTk(fg_color="black")
app.geometry("700x550")
app.title("YouTube Download")
app.after(201, lambda :app.iconbitmap("normally would show the path to the file but removed for privacy"))

font = customtkinter.CTkFont(family='Trade Gothic LT Bold Condensed No. 20', size=30)
font2 = customtkinter.CTkFont(family='Trade Gothic LT Bold Condensed No. 20', size=20)
menu = ''
currenttype = ''


def MainMenuGUI():
    menu = 'main'

    title.grid_forget()
    finishLabel.grid_forget()
    Vprogbar.grid_forget()
    link.grid_forget()
    download.grid_forget()
    backbutton.grid_forget()
    pPercent.grid_forget()

    app.grid_rowconfigure(0, weight=1)
    app.grid_rowconfigure(1, weight=1)
    app.grid_rowconfigure(2, weight=1)
    app.grid_columnconfigure(0, weight=1)
    app.grid_columnconfigure(1, weight=1)

    WhatLabel.grid(row=0, column=0, columnspan=2, padx=100, pady=50)
    Videobutton.grid(row=1, column=1, sticky=customtkinter.W, padx=50)
    AudioButton.grid(row=1, column=0, sticky=customtkinter.E, padx=50)
    PVideobutton.grid(row=2, column=1, sticky=customtkinter.W, padx=50, pady=50)
    PAudioButton.grid(row=2, column=0, sticky=customtkinter.E, padx=50, pady=50)

def mp4_to_mp3(mp4, mp3):
    mp4_without_frames = AudioFileClip(mp4)
    mp4_without_frames.write_audiofile(mp3)
    mp4_without_frames.close() # function call mp4_to_mp3("my_mp4_path.mp4", "audio.mp3")

def DownloadGUI(dtype):
    global menu
    menu = dtype
    global currenttype
    currenttype = dtype

    WhatLabel.grid_forget()
    Videobutton.grid_forget()
    AudioButton.grid_forget()
    PVideobutton.grid_forget()
    PAudioButton.grid_forget()
    Vprogbar.grid_forget()
    pPercent.grid_forget()
    Pprogbar.grid_forget()

    app.grid_rowconfigure(0, weight=100)
    app.grid_rowconfigure(1, weight=10)
    app.grid_rowconfigure(2, weight=1)
    app.grid_rowconfigure(3, weight=100)

    title.grid(row=0, column=0, columnspan=2, sticky=customtkinter.S)
    link.grid(row=1, column=0, columnspan=2)
    download.grid(row=2, column=0, columnspan=2, sticky=customtkinter.N)
    finishLabel.grid(row=3, column=0, columnspan=2, sticky=customtkinter.N)
    backbutton.grid(row=3, column=1, columnspan=1)

    finishLabel.configure(text='')
    if currenttype == "Video" or currenttype == "Audio":
        title.configure(text="Insert a YouTube link")
        download.configure(command=lambda: startdownload(dtype=currenttype))
    else:
        title.configure(text="Insert a playlist link")
        download.configure(command=lambda: startdownloadP(dtype=currenttype))

    link.delete(0, customtkinter.END)

def startdownload(dtype):
    try:
        ytlink = link.get()
        ytobject = YouTube(ytlink, on_progress_callback=Vprog)

        title.configure(text=ytobject.title)
        finishLabel.configure(text="")

        global menu
        menu = str(dtype) + 'Download'

        path = tkinter.filedialog.askdirectory(initialdir="/", mustexist=True, parent=app)

        pPercent.grid(row=1, column=0, columnspan=2, sticky=customtkinter.S)
        link.grid_forget()
        download.grid_forget()
        Vprogbar.grid(row=2, column=0, columnspan=2, sticky=customtkinter.N)

        if dtype == "Video":
            video = ytobject.streams.get_highest_resolution()
            video.download(output_path=str(path))

        elif dtype == "Audio":
            audio = ytobject.streams.get_audio_only()

            if os.path.exists(str(path) + "/" + sanitize_filename(audio.title) + ".mp4"):
                audio.download(output_path=str(path), filename=sanitize_filename(audio.title) + "1" + ".mp4")
                clip = AudioFileClip(str(path) + "/" + sanitize_filename(audio.title) + "1" + ".mp4")
                clip.write_audiofile(str(path) + "/" + sanitize_filename(audio.title) + ".mp4"[:-4] + ".mp3")
                clip.close()
                if os.path.exists(str(path)+"/"+sanitize_filename(audio.title) + "1" + ".mp4"):
                    os.remove(str(path)+"/"+sanitize_filename(audio.title) + "1" + ".mp4")
            else:
                audio.download(output_path=str(path), filename=sanitize_filename(audio.title)+".mp4")
                clip = AudioFileClip(str(path)+"/"+sanitize_filename(audio.title)+".mp4")
                clip.write_audiofile(str(path)+"/"+sanitize_filename(audio.title)+".mp4"[:-4] + ".mp3")
                clip.close()
                if os.path.exists(str(path)+"/"+sanitize_filename(audio.title)+".mp4"):
                    os.remove(str(path)+"/"+sanitize_filename(audio.title)+".mp4")



        finishLabel.configure(text="downloaded", text_color="white")

    except:
        finishLabel.configure(text="Download has failed", text_color="red")
        title.configure(text="Insert a YouTube link")
        Vprogbar.grid_forget()
        link.grid(row=1, column=0, columnspan=2)

def startdownloadP(dtype):
    vcount = 0
    vccount = 0
    playlink = Playlist(link.get())
    path = tkinter.filedialog.askdirectory(initialdir="/", mustexist=True, parent=app)

    pPercent.grid(row=1, column=0, columnspan=2, sticky=customtkinter.S)
    ppPercent.grid(row=3, column=0, columnspan=2, sticky=customtkinter.N, pady=20)
    link.grid_forget()
    download.grid_forget()
    Vprogbar.grid(row=2, column=0, columnspan=2, sticky=customtkinter.N)
    Pprogbar.grid(row=3, column=0, columnspan=2, sticky=customtkinter.N, pady=10)

    for _ in playlink.videos:
        vcount += 1

    Pprog(vcount, 0)

    for vid in playlink.videos:
        vccount += 1
        Pprog(vcount, vccount)
        vid.register_on_progress_callback(Vprog)
        title.configure(text=vid.title)

        global menu
        menu = str(dtype) + 'Download'

        if dtype == "VideoPlaylist":
            video = vid.streams.get_highest_resolution()
            video.download(output_path=str(path))

        elif dtype == "AudioPlaylist":
            audio = vid.streams.get_audio_only()

            if os.path.exists(str(path) + "/" + sanitize_filename(audio.title) + ".mp4"):
                audio.download(output_path=str(path), filename=sanitize_filename(audio.title) + "1" + ".mp4")
                clip = AudioFileClip(str(path) + "/" + sanitize_filename(audio.title) + "1" + ".mp4")
                clip.write_audiofile(str(path) + "/" + sanitize_filename(audio.title) + ".mp4"[:-4] + ".mp3")
                clip.close()
                if os.path.exists(str(path) + "/" + sanitize_filename(audio.title) + "1" + ".mp4"):
                    os.remove(str(path) + "/" + sanitize_filename(audio.title) + "1" + ".mp4")
            else:
                audio.download(output_path=str(path), filename=sanitize_filename(audio.title) + ".mp4")
                clip = AudioFileClip(str(path) + "/" + sanitize_filename(audio.title) + ".mp4")
                clip.write_audiofile(str(path) + "/" + sanitize_filename(audio.title) + ".mp4"[:-4] + ".mp3")
                clip.close()
                if os.path.exists(str(path) + "/" + sanitize_filename(audio.title) + ".mp4"):
                    os.remove(str(path) + "/" + sanitize_filename(audio.title) + ".mp4")

    pPercent.grid_forget()
    ppPercent.grid_forget()
    Vprogbar.grid_forget()
    Pprogbar.grid_forget()

    finishLabel.grid(row=2, column=0, columnspan=2, sticky=customtkinter.N)
    finishLabel.configure(text="All videos downloaded", text_color="white")


def Vprog(stream, chunk, bytes_remaining):
    total_size = stream.filesize
    bytes_downloaded = total_size - bytes_remaining
    percentage_complete = bytes_downloaded / total_size * 100
    per = str(int(percentage_complete))
    pPercent.configure(text=per + "%")
    pPercent.update()

    Vprogbar.set(float(percentage_complete / 100))

    pPercent.update()
    Vprogbar.update()
def Pprog(total, current):
    percentage_done = current / total * 100
    per = str(int(percentage_done))
    ppPercent.configure(text=per + "%")
    Pprogbar.set(float(percentage_done / 100))
    Pprogbar.update()


def Back():
    if 'Download' in menu:
        DownloadGUI(menu[:-8])
    else:
        MainMenuGUI()



urlvar = tkinter.StringVar()

title = customtkinter.CTkLabel(app, text="Insert a YouTube link", fg_color='White',corner_radius=10, padx=50, font=font)
link = customtkinter.CTkEntry(app, width=350, height=40, textvariable=urlvar)
finishLabel = customtkinter.CTkLabel(app, text="", text_color="white")
pPercent = customtkinter.CTkLabel(app, text="0%", text_color="white")
ppPercent = customtkinter.CTkLabel(app, text="0%", text_color="white")
Vprogbar = customtkinter.CTkProgressBar(app, width=400, progress_color="red")
Pprogbar = customtkinter.CTkProgressBar(app, width=400, progress_color="red")
download = customtkinter.CTkButton(app, text="Download", command=lambda: startdownload(dtype=currenttype), fg_color="#e8e6e6", text_color="black", hover_color='#c0c0c0')
Vprogbar.set(0)
Pprogbar.set(0)

backbutton = customtkinter.CTkButton(app, text_color='black', text="back", width=50, height=50, fg_color='#333333', hover_color='#c0c0c0', command=Back)

WhatLabel = customtkinter.CTkLabel(app, text="What would you like to download?", fg_color='White',corner_radius=10, padx=50, font=font)
Videobutton = customtkinter.CTkButton(app, text='Video', width=250, height=150, font=font2, fg_color='#b01214', hover_color='#590404', command=lambda: DownloadGUI(dtype='Video'))
AudioButton = customtkinter.CTkButton(app, text='Audio', width=250, height=150, font=font2, fg_color='#b01214', hover_color='#590404', command=lambda: DownloadGUI(dtype='Audio'))
PVideobutton = customtkinter.CTkButton(app, text='Video Playlist', width=250, height=150, font=font2, fg_color='#b01214', hover_color='#590404', command=lambda: DownloadGUI(dtype='VideoPlaylist'))
PAudioButton = customtkinter.CTkButton(app, text='Audio Playlist', width=250, height=150, font=font2, fg_color='#b01214', hover_color='#590404', command=lambda: DownloadGUI(dtype='AudioPlaylist'))

#https://www.youtube.com/watch?v=cPAlmXHMktA

#https://www.youtube.com/playlist?list=PLpi4n5vOXXFkJxTSD8VRuZ9phHmojXaP1

#https://www.youtube.com/watch?v=Ga2PA-vEiFk

#https://www.youtube.com/watch?v=QU8pe3dhz8s&list=PLpi4n5vOXXFkJxTSD8VRuZ9phHmojXaP1&index=10

MainMenuGUI()

app.mainloop()

I don't know what to do because honestly I don't have that much knowledge on what is going on in the background of the libraries I am using, and Googling the problem seems to come up with no answers.


Solution

  • According to this blogpost, the fix should be easy. It's either this:

    ModuleNotFoundError: No module named x / ImportError: No module named x

    This means a particular module ('x' in this case) was not added to the package. I have seen this occur with packages in the pandas library and win32api; as long as you can identify the package (e.g. 'x'), then it is very easy to fix.

    To fix this in the UI, open the advanced tab and find the --hidden-import input. Simply paste the module name into this input and then repackage. If the original error is still appearing, you have done this incorrectly.

    For example, if you are missing pandas._libs.tslib, add 'pandas._libs.tslib' into the input by --hidden-import. Additionally, you can add more than one module, for example, pandas._libs.tslib, win32api. (See the question mark by the input for more information).

    Or this:

    Alternatively, you may have installed auto-py-to-exe in one Python environment (a single installation or venv) and installed your dependent package in a different Python environment. Take a look at "How to Manage Multiple Python Distributions" for help on how to identify if you've done this.

    https://nitratine.net/blog/post/issues-when-using-auto-py-to-exe/?utm_source=auto_py_to_exe&utm_medium=readme_link&utm_campaign=auto_py_to_exe_help#modulenotfounderror-no-module-named-x-importerror-no-module-named-x