Esempio n. 1
0
class Application(tk.Frame):
    def __init__(self, master):

        # master is set to root(tk.TK()) at the bottom
        super().__init__(master)
        self.master = master
        self.js = JsonMaker()

        # init variables
        global isSelected
        isSelected = False

        # Widget Title
        master.title('RSS Feed Reader')
        # Width height
        master.geometry("1600x900")
        # change background color of root, color from https://html-color-codes.info/
        master.configure(bg="#585858")
        # set title bar icon
        master.iconbitmap('Icons/RSS_Icon.ico')

        # Create widgets/grid
        self.create_widgets()
        # Init selected item var
        self.selected_item = 0
        # Populate initial list
        self.populate_list()

    # region Functions

    # Create GUI
    def create_widgets(self):

        # Frames

        self.frm = tk.Frame(self.master, bg="#585858")
        self.frm.pack()

        # GUI elements

        self.spaceFrame = tk.Label(self.frm,
                                   text="RSS Entries Viewer",
                                   relief=GROOVE,
                                   anchor=CENTER,
                                   font='Times 12 italic',
                                   bg='#424242',
                                   fg='#FFFFFF')
        self.spaceFrame.grid(row=0, column=3, columnspan=9, pady=5, ipadx=10)

        self.spaceFrame = tk.Label(self.frm,
                                   text="Create Databases!",
                                   relief=GROOVE,
                                   anchor=CENTER,
                                   font='Times 12 italic',
                                   bg='#424242',
                                   fg='#FFFFFF')
        self.spaceFrame.grid(row=1, column=12, columnspan=3, pady=5, ipadx=10)

        self.spaceFrame = tk.Label(self.frm,
                                   text="Delete Files!",
                                   relief=GROOVE,
                                   anchor=CENTER,
                                   font='Times 12 italic',
                                   bg='#424242',
                                   fg='#FFFFFF')
        self.spaceFrame.grid(row=1,
                             column=1,
                             columnspan=1,
                             pady=5,
                             ipadx=10,
                             padx=40)

        self.optionListbox = Listbox(self.frm,
                                     bg='#D8D8D8',
                                     height="25",
                                     width=150,
                                     border=0)
        self.optionListbox.grid(row=3,
                                column=2,
                                columnspan=10,
                                rowspan=6,
                                pady=5,
                                padx=10)

        self.tv = ttk.Treeview(self.frm,
                               columns=(1, 2, 3, 4),
                               show="headings",
                               height="20",
                               style="Treeview")

        self.SummaryBox = Text(self.frm, height=10, width=100)
        self.SummaryBox.grid(row=10,
                             column=2,
                             columnspan=10,
                             rowspan=6,
                             pady=5,
                             padx=20)

        self.enterString = Entry(self.frm, width=90)
        self.enterString.grid(row=1, column=4, columnspan=6, pady=5)

        self.clearInput = tk.Button(self.frm,
                                    text="Clear Text",
                                    command=self.clearTextbox)
        self.clearInput.grid(row=2, column=9, pady=15, padx=10)

        self.enterString_label = Label(self.frm,
                                       text="Enter RSS URL and Strings:",
                                       bg='#424242',
                                       fg='#FFFFFF')
        self.enterString_label.grid(row=1, column=3, pady=5, padx=10, ipadx=20)

        self.inputURL = tk.Button(self.frm,
                                  text="Enter RSS URL",
                                  command=self.enterURL)
        self.inputURL.grid(row=2, column=5, pady=15)

        self.cdbBtn = tk.Button(self.frm,
                                text="Create DB",
                                command=self.CreateDB)
        self.cdbBtn.grid(row=5, column=12, pady=5, padx=60)

        self.clearDBBtn = tk.Button(self.frm,
                                    text="Clear DB",
                                    command=self.ClearDB)
        self.clearDBBtn.grid(row=4, column=1, pady=5)

        self.clearGridBtn = tk.Button(self.frm,
                                      text="Table Name",
                                      command=self.getName)
        self.clearGridBtn.grid(row=2, column=3, pady=15)

        # need to expand the functionality to work with multiple tables in the same DB before using this function in the full build!
        # self.optionsunoBtn = tk.Button(self.frm, text="From Directory", command=self.findDirectory)
        # self.optionsunoBtn.grid(row=8, column=1, pady = 5)

        self.JSONBtn = tk.Button(self.frm,
                                 text="Make JSON",
                                 command=self.makeJson)
        self.JSONBtn.grid(row=3, column=12, pady=5)

        self.simpleBtn = tk.Button(self.frm,
                                   text="JSON Entries",
                                   command=self.makeJsonEntries)
        self.simpleBtn.grid(row=4, column=12, pady=5)

        self.clearGridBtn = tk.Button(self.frm,
                                      text="Clear Grid",
                                      command=self.clearGrid)
        self.clearGridBtn.grid(row=7, column=1, pady=5)

        # delete this button and all it's functions when the project is fully complete, this is for testing only!
        # self.clearGridBtn = tk.Button(self.frm, text="test vars", command=self.testVar)
        # self.clearGridBtn.grid(row=9, column=1, pady = 5)

        self.DBNameBtn = tk.Button(self.frm,
                                   text="DB Name",
                                   command=self.nameDB)
        self.DBNameBtn.grid(row=2, column=7, pady=15)

        self.EditDBBtn = tk.Button(self.frm,
                                   text="Edit DB",
                                   command=self.editDBWindow)
        self.EditDBBtn.grid(row=6, column=12, pady=5)

        self.clearJSONBtn = tk.Button(self.frm,
                                      text="Clear JSON",
                                      command=self.ClearJSON)
        self.clearJSONBtn.grid(row=5, column=1, pady=5)

        self.clearCSVBtn = tk.Button(self.frm,
                                     text="Clear CSV",
                                     command=self.ClearCSV)
        self.clearCSVBtn.grid(row=6, column=1, pady=5)

        self.copyLinkBtn = tk.Button(self.frm,
                                     text="Copy URL",
                                     command=self.copy_info)
        self.copyLinkBtn.grid(row=8, column=12, pady=5)

        self.ShowDBBtn = tk.Button(self.frm,
                                   text="Show DB",
                                   command=self.showDB)
        self.ShowDBBtn.grid(row=7, column=12, pady=5)

        self.removeEntryBtn = tk.Button(self.frm,
                                        text="Delete Entry!",
                                        command=self.deleteEntry)
        self.removeEntryBtn.grid(row=3, column=1, pady=5)

        self.spaceFrame = tk.Label(self.frm,
                                   text="Instructions:",
                                   relief=GROOVE,
                                   anchor=CENTER,
                                   font='Times 12 italic',
                                   bg='#424242',
                                   fg='#FFFFFF')
        self.spaceFrame.grid(row=18, column=3, columnspan=9, pady=5, ipadx=10)

        self.spaceFrame = tk.Label(
            self.frm,
            text=
            "Enter names for Database, Table, and the URL for RSS Feed using the first three buttons in the top row!",
            relief=GROOVE,
            anchor=CENTER,
            font='Times 12',
            bg='#424242',
            fg='#FFFFFF')
        self.spaceFrame.grid(row=19, column=3, columnspan=9, pady=2, ipadx=10)

        self.spaceFrame = tk.Label(
            self.frm,
            text=
            "After the names/URL are inserted, Click on the right side, then 'Make JSON', then 'JSON Entries', and then 'Create DB' in order to show the entries in the RSS Feed!",
            relief=GROOVE,
            anchor=CENTER,
            font='Times 12',
            bg='#424242',
            fg='#FFFFFF')
        self.spaceFrame.grid(row=20, column=3, columnspan=9, pady=2, ipadx=10)

        self.spaceFrame = tk.Label(
            self.frm,
            text=
            "Use 'Find Directory' button to get the main.JSON file from another table name inserted if you want to select another table, the rest of the buttons can be figured out by testing them manually!",
            relief=GROOVE,
            anchor=CENTER,
            font='Times 12',
            bg='#424242',
            fg='#FFFFFF')
        self.spaceFrame.grid(row=21, column=1, columnspan=17, pady=2, ipadx=10)

    # function to handle event that the user enters the RSS URL in textbox
    def enterURL(self):

        global entry

        if not self.enterString.get() == "":

            entry = self.enterString.get()
            self.clearTextbox()

    # function to get names from entry box
    def getName(self):

        global name

        if not self.enterString.get() == "":

            name = self.enterString.get()
            self.clearTextbox()

    # function to clear the textbox
    def clearTextbox(self):

        self.enterString.delete(0, END)

    # function to populate listbox with data from database if availible
    def populate_list(self):

        global DatabaseName
        global name

        if os.path.isdir('DB_Files/'):
            if not len(os.listdir('DB_Files/')) == 0:
                if not name == "" and not DatabaseName == "":
                    self.showDB()
                else:
                    DatabaseName, name = self.js.getDBandName()
                    print(DatabaseName + " " + name)
                    self.populate_list()
            else:
                pass
        else:
            pass

    # function to take in url and convert it to json
    def makeJson(self):

        self.js.makeWholeJSON(name, entry)

    # function to make json file for each entry in the feed
    def makeJsonEntries(self):

        self.js.getEntries()

    # function to set NewsFeed in Json.py to the main.JSON file located in directory specificed by user input
    def findDirectory(self):

        if not self.enterString.get() == "":

            indexValue = self.enterString.get()
            self.js.readFromJson(indexValue)
            self.clearTextbox()

    # function to get Database name from User
    def nameDB(self):

        global DatabaseName

        if not self.enterString.get() == "":

            DatabaseName = self.enterString.get()
            self.clearTextbox()

    # function to handle copying the URL of the entry from table the user wants
    def copy_info(self):

        if not currentLink == "":
            # save link from values to system clipboard
            pyperclip.copy(currentLink)

    # function to clear the JSON files from the directory
    def ClearJSON(self):

        self.js.wipeJSON()

    # function to clear the CSV files from the directory
    def ClearCSV(self):

        self.js.wipeCSV()

        # function to create database

    # function to create the database!
    def CreateDB(self):

        if not DatabaseName == "":

            if not name == "":

                table = name
                self.js.makeDB(DatabaseName, table)
                self.js.convertToSQL(DatabaseName, table)
                self.showDB()
                self.clearTextbox()
            else:
                print("Database creation failed!")
        else:
            print("Database name was null!")

    # function to clear the DB files from the directory
    def ClearDB(self):

        self.js.wipeDB()
        self.clearGrid()

    # function to clear the grid
    def clearGrid(self):

        self.tv.delete(
            *self.tv.get_children())  # delete saved treeview children
        self.tv.grid_remove()
        self.optionListbox.grid_remove()
        self.SummaryBox.delete("1.0", "end")
        self.optionListbox.grid(row=3,
                                column=2,
                                columnspan=10,
                                rowspan=6,
                                pady=5,
                                padx=10)

    # function to display content from db
    def showDB(self):

        rows = []

        self.clearGrid()

        # style the output graph, style name is "Treeview" as defined in style.configure
        self.style = ttk.Style()
        self.style.configure(".", font=('Helvetica', 8), foreground="white")
        self.style.configure("Treeview", foreground='red')
        self.style.layout("Treeview", [("Treeview.treearea", {
            'sticky': 'nswe'
        })])
        self.style.configure("Treeview.Heading",
                             foreground='green',
                             font='bold',
                             stretch=tk.YES)

        self.tv.grid(row=3,
                     column=2,
                     columnspan=10,
                     rowspan=6,
                     pady=5,
                     padx=10)

        self.tv.heading(1, text="Index")
        self.tv.column("1", minwidth=0, width=50, stretch=NO)
        self.tv.heading(2, text="Title")
        self.tv.column("2", minwidth=0, width=400, stretch=YES)
        self.tv.heading(3, text="Date")
        self.tv.column("3", minwidth=0, width=200, stretch=YES)
        self.tv.heading(4, text="Link")
        self.tv.column("4", minwidth=0, width=500, stretch=YES)

        self.tv.bind('<ButtonRelease-1>', self.select_item)

        rows = self.js.showTable(DatabaseName, name)

        for i in rows:
            self.tv.insert('', 'end', values=i)

        self.clearTextbox()

    # function to select row from list
    def select_item(self, a):

        global currentIndex
        global currentTitle
        global currentDate
        global currentLink
        global cleansummary
        global isSelected

        isSelected = True
        # print(isSelected)

        # rowSelection is set to the row the user left clicks on
        self.rowSelection = self.tv.selection()[0]
        cursel = self.tv.item(self.rowSelection)

        currentIndex = 0
        currentIndex = cursel['values'][0]
        # print("The Current Index is: ", currentIndex)

        currentTitle = ""
        currentTitle = cursel['values'][1]
        # print("The Current Title is: ", currentTitle)

        currentDate = ""
        currentDate = cursel['values'][2]
        # print("The Current Date is: ", currentDate)

        currentLink = ""
        currentLink = cursel['values'][3]
        # print("The Current Link is: ", currentLink)

        cleansummary = ""  # init the textbox
        self.SummaryBox.delete("1.0", "end")
        summary = cursel['values'][4]
        cleansummary = BeautifulSoup(
            summary, "lxml").text  # clean summary text with BS4
        self.SummaryBox.insert(tk.END, cleansummary)

    # function to delete an entry
    def deleteEntry(self):

        global isSelected

        if isSelected == True:

            if not DatabaseName == "" and not name == "":

                self.js.deleteDatabaseEntry(DatabaseName, name, currentIndex)
                self.showDB()
            else:
                pass
        else:
            pass
            print("isSelected was False")

        isSelected = False

    # region editDB window functions

    # function to create edit DB window:
    def editDBWindow(self):

        self.newWindow = tk.Toplevel(app)
        # Widget Title
        self.newWindow.title('Modify Database')
        # Width height
        self.newWindow.geometry("800x450")
        # change background color of root, color from https://html-color-codes.info/
        self.newWindow.configure(bg="#585858")
        # set title bar icon
        self.newWindow.iconbitmap('Icons/RSS_Icon.ico')

        self.wfrm = tk.Frame(self.newWindow, bg="#585858")
        self.wfrm.pack()

        self.wFrame = tk.Label(self.wfrm,
                               text="Modify Database Entry!",
                               relief=GROOVE,
                               anchor=CENTER,
                               font='Times 12 italic',
                               bg='#424242',
                               fg='#FFFFFF')
        self.wFrame.grid(row=0, column=2, columnspan=2, pady=5, ipadx=10)

        self.new_title_label = Label(self.wfrm,
                                     text="Enter new Title:",
                                     bg='#424242',
                                     fg='#FFFFFF')
        self.new_title_label.grid(row=1, column=1, pady=5, padx=10)

        self.new_title_String = Entry(self.wfrm, width=45)
        self.new_title_String.grid(row=1, column=3, columnspan=1, pady=5)

        self.new_date_label = Label(self.wfrm,
                                    text="Enter new Date:",
                                    bg='#424242',
                                    fg='#FFFFFF')
        self.new_date_label.grid(row=2, column=1, pady=5, padx=10)

        self.new_date_String = Entry(self.wfrm, width=45)
        self.new_date_String.grid(row=2, column=3, columnspan=1, pady=5)

        self.new_URL_label = Label(self.wfrm,
                                   text="Enter new URL:",
                                   bg='#424242',
                                   fg='#FFFFFF')
        self.new_URL_label.grid(row=3, column=1, pady=5, padx=10)

        self.new_URL_String = Entry(self.wfrm, width=45)
        self.new_URL_String.grid(row=3, column=3, columnspan=1, pady=5)

        self.new_summary_label = Label(self.wfrm,
                                       text="Enter new Summary:",
                                       bg='#424242',
                                       fg='#FFFFFF')
        self.new_summary_label.grid(row=4, column=1, pady=5, padx=10)

        self.new_summary_Box = Text(self.wfrm, height=10, width=70)
        self.new_summary_Box.grid(row=5,
                                  column=1,
                                  columnspan=5,
                                  rowspan=6,
                                  pady=5,
                                  padx=20)

        self.saveChangesBtn = tk.Button(self.wfrm,
                                        text="Save Changes!",
                                        width=18,
                                        command=self.saveChanges)
        self.saveChangesBtn.grid(row=12, column=4, pady=10)

        self.deleteEntryBtn = tk.Button(self.wfrm,
                                        text="Quit!",
                                        width=18,
                                        command=self.quitWindow)
        self.deleteEntryBtn.grid(row=12, column=1, pady=10)

    # function to save changes to the database
    def saveChanges(self):

        global currentTitle
        global currentDate
        global currentLink
        global cleansummary
        global isSelected

        if isSelected == True:

            currentTitle = self.new_title_String.get()
            currentDate = self.new_date_String.get()
            currentLink = self.new_URL_String.get()
            cleansummary = self.new_summary_Box.get("1.0", END)

            self.new_title_String.delete(0, END)
            self.new_date_String.delete(0, END)
            self.new_URL_String.delete(0, END)
            self.new_summary_Box.delete("1.0", "end")

            # print("")
            # print("Database name is ",DatabaseName)
            # print("Table name is ", name)
            # print("Current Index: ",currentIndex)
            # print("new Title: ",currentTitle)
            # print("new Date: ",currentDate)
            # print("new URL: ",currentLink)
            # print("new Summary: ",cleansummary)

            self.js.updateTable(DatabaseName, name, currentIndex, currentTitle,
                                currentDate, currentLink, cleansummary)
            self.showDB()
        else:
            pass
            print("isSelected was False")

        isSelected = False

    # function to quit the newwindow
    def quitWindow(self):

        self.newWindow.destroy()

    # endregion

    # function to test the values of all variables
    def testVar(self):

        print("URL is: ", entry)
        print("Name is: ", name)
        print("Database Name is: ", DatabaseName)
        self.js.testVariables()
Esempio n. 2
0
class PytubeGUI:
    """
        Minimalist GUI for pytube
    """

    def __init__(self, root):
        self.master = root

        self.master.title("PyTube-GUI")
        self.master.iconbitmap("pylogo.ico")

        self.master.geometry("450x125")
        self.master.minsize(width=450, height=125)
        self.master.resizable(width=True, height=False)
        (width_offset, height_offset) = self.get_offsets()
        self.master.geometry(f"+{width_offset}+{height_offset}")
        self.master.update()

        #Widgets
        self.mode = StringVar(self.master)
        self.mode.set("Video + Audio")

        self.link_entry_x_scroll = Scrollbar(
            self.master,
            orient="horizontal"
        )

        self.link_entry = Entry(
            self.master,
            font="Sans_Serif 12",
            xscrollcommand=self.link_entry_x_scroll.set
        )

        self.link_entry_x_scroll.config(command=self.link_entry.xview)

        self.mode_menu = OptionMenu(
            self.master,
            self.mode,
            "Video + Audio",
            "Audio Only",
            "HQ"
        )
        self.mode_menu.config(width=12)

        self.mode_label = Label(
            self.master,
            text="Mode:",
            font="Sans_Serif 10",
        )

        self.status_label = Label(
            self.master,
            text="Ready",
            font="Sans_Serif 12",
            padx=10,
            relief="sunken"
        )

        self.link_entry_button = Button(
            self.master,
            text="Submit",
            width=15,
            command=self.submit
        )

        self.dir_entry_x_scroll = Scrollbar(
            self.master,
            orient="horizontal"
        )

        self.dir_entry = Entry(
            self.master,
            font="Sans_Serif 12",
            xscrollcommand=self.dir_entry_x_scroll.set
        )

        self.dir_entry_x_scroll.config(command=self.dir_entry.xview)

        self.dir_entry_button = Button(
            self.master,
            text="Select Directory",
            width=15,
            command=self.dir_select
        )

        #HQ mode widgets:
        self.stream_list_y_scroll = Scrollbar(
            self.master,
            orient="vertical"
        )

        self.stream_list = Listbox(
            self.master,
            yscrollcommand=self.stream_list_y_scroll.set
        )

        self.stream_list_y_scroll.config(command=self.stream_list.yview)
        self.stream_selection = StringVar(self.master)

        #Layout:
        self.master.columnconfigure(0, weight=1)
        self.master.rowconfigure(5, weight=1)

        self.link_entry.grid(row=0, column=0, sticky="ew")
        self.link_entry_x_scroll.grid(row=1, column=0, sticky="ew")
        self.link_entry_button.grid(row=0, column=1)
        self.mode_label.grid(row=1, column=1, sticky="ew")
        self.mode_menu.grid(row=2, column=1)
        self.status_label.grid(row=2, column=0)
        self.dir_entry.grid(row=3, column=0, sticky="ew")
        self.dir_entry_x_scroll.grid(row=4, column=0, sticky="ew")
        self.dir_entry_button.grid(row=3, column=1)    

        #Bindings, Traces, Protocols:
        self.link_entry.bind("<Return>", self.submit)
        self.stream_list.bind("<Return>", self.update_stream_selection)
        self.stream_list.bind("<Double-Button-1>", self.update_stream_selection)
        self.mode.trace("w", self.set_gui)
        self.master.bind("<Control-w>", self.close)
        self.master.protocol("WM_DELETE_WINDOW", self.close)

        #Probe ffmpeg:
        self.ffmpeg = which("ffmpeg")
        if not self.ffmpeg:
            messagebox.showwarning(
                "Error", 
                "Unable to probe ffmpeg.\n\n"\
                "HQ & Audio Only most likely will not function "\
                "correctly without ffmpeg.exe on the system path."
            )
            self.mode_menu.config(state="disabled")


    def __str__(self):
        """
            Pretty print self w/ address
        """
        
        return f"PyTube-GUI @ {hex(id(self))}"


    def set_gui(self, *args):
        """
            Swap between GUI layouts (Stream list / No stream list)
        """

        if self.mode.get() == "HQ":
            self.master.resizable(width=True, height=True)
            self.master.geometry("450x250")
            self.stream_list.grid(row=5,column=0, columnspan=2, sticky="nsew")
            self.master.resizable(width=True, height=False)
        else:
            self.master.resizable(width=True, height=True)
            self.master.geometry("450x125")
            self.stream_list.grid_remove()
            self.master.resizable(width=True, height=False)


    def hq_download(self, yt_vid, directory):
        """
            Slower download rate on average, higher resolution + ffmpeg zip
        """

        self.update_status_label("Select Stream")
        self.stream_list.delete(0,"end")
        
        streams = yt_vid.streams.filter(
            adaptive=True,
            type="video",
            subtype="mp4"
        )

        for stream in streams:
            self.stream_list.insert("end",stream)

        self.master.update()
        self.master.wait_variable(self.stream_selection)

        if self.stream_selection.get() == "__NONE__":
            return self.stream_selection.get()

        itag = findall("itag=\"\d*\"", self.stream_selection.get())
        itag = sub("itag=\"|\"","",itag[0])

        hq_stream = yt_vid.streams.get_by_itag(itag)

        path = directory + "/" + clean_file_name(unescape(hq_stream.title)) + ".mp4"
        (filename, full_path) = name_file(path)

        #Try to get video stream first:
        hq_path = None
        try:
            hq_path = hq_stream.download(directory, filename=filename+" VIDEO")

        except Exception as err:
            messagebox.showwarning("Error", err)
            
        #If we have the video stream, then go for the audio as well:

        audio_path = None
        if hq_path:
            try:
                audio_path = yt_vid \
                    .streams \
                    .filter(only_audio=True, subtype="mp4") \
                    .order_by("abr") \
                    .desc() \
                    .first() \
                    .download(directory, filename=filename+" AUDIO")

            except Exception as err:
                messagebox.showwarning("Error", err)
            
        #If we get here with both, zip them:
        if hq_path and audio_path:
            run([
                f"ffmpeg",
                "-i",
                hq_path,
                "-i",
                audio_path,
                "-codec",
                "copy",
                full_path
            ])

            run(["del", hq_path],shell=True)
            run(["del", audio_path],shell=True)


    def download(self, yt_vid, directory):
        """
            Quicker download, lower resolution
        """

        stream = yt_vid.streams.filter(
            progressive="True",
            file_extension="mp4"
        ).first()

        path = directory + "/" + clean_file_name(unescape(stream.title)) + ".mp4"
        (filename, full_path) = name_file(path)

        try:
            stream.download(directory, filename=filename)

        except Exception as err:
            messagebox.showwarning("Error", err)

        else:
            if self.mode.get() == "Audio Only":
                #Audio only streams download rather slowly via pytube,
                #therefore, we take the audio in place w/ pydub:
                self.update_status_label("Extracting Audio...")
                audio = AudioSegment.from_file(full_path)
                audio.export(full_path, format="mp4")


    def disabled_ui(fn):
        """
            Wrapper for submit() that prevents accidental repeat downloads.

            If we spam click submit, we'll end up with a bunch of duplicates, which
            probably isn't desirable. 
        """

        @wraps(fn)
        def inner(self, *args, **kwargs):

            self.link_entry_button.config(state="disabled")

            fn(self, *args, **kwargs)

            self.update_status_label("Ready")
            self.stream_list.delete(0,"end")
            self.link_entry.delete(0, "end")
            self.link_entry_button.config(state="normal")
        
        return inner


    @disabled_ui
    def submit(self):
        """
            Controller/handler for download() & hq_download()
        """

        if not self.link_entry.get():
            return
        if not url(self.link_entry.get()):
            messagebox.showwarning(
                "Invalid Input",
                "Input is not a valid URL."
            )
            self.link_entry.selection_range(0,"end")
            return

        self.update_status_label("Fetching")

        try:
            yt_vid = YouTube(self.link_entry.get())
        except KeyError as err:
            return messagebox.showwarning("KeyError", err)

        except Exception as err:
            #Same as above, capture & return
            return messagebox.showwarning(
                "Invalid Link",
                f"Unable to fetch video.\n{err}"
            )
            
        yt_vid.register_on_progress_callback(self.progress)

        directory = self.dir_entry.get() or self.dir_select()
        if not directory:
            self.update_status_label("Ready")
            return
        if not exists(directory):
            return messagebox.showwarning(
                "Invalid Path",
                "The specified directory cannot be found."
            )

        if self.mode.get() == "HQ":
            self.hq_download(yt_vid, directory)
        else:
            self.download(yt_vid, directory)


    def close(self, event=None):
        """
            Callback for window close, satisfies wait_variable()
            before killing the application.
        """
        
        self.stream_selection.set("__NONE__")
        raise SystemExit


    def update_stream_selection(self,*args):
        """
            Callback for Double-Click / Enter key on stream_list.
            Serves to unblock wait_variable in hq_download()
        """

        self.stream_selection.set(
            self.stream_list.get(self.stream_list.curselection())
        )


    def dir_select(self):
        """
            A wrapper for askdirectory() that populates the GUI.
        """

        directory = askdirectory()
        if directory:
            self.dir_entry.delete(0, "end")
            self.dir_entry.insert(0, directory)
            return directory


    def update_status_label(self, status):
        """
            DRYs up two function calls, adds some dynamic behavior.
        """

        self.status_label.config(text=status)
        self.status_label.update()


    def progress(self, stream, chunk, bytes_remaining):
        """
            Callback for download progress
        """

        dl_progress = round(
            (100*(stream.filesize - bytes_remaining)) / stream.filesize,
            2
        )

        self.update_status_label(f"Downloading... {dl_progress} %")


    def get_offsets(self):
        """
            Returns an appropriate offset for a given tkinter toplevel,
            such that it always is created center screen on the primary display.
        """

        width_offset = int(
            (self.master.winfo_screenwidth() / 2) - (self.master.winfo_width() / 2)
        )

        height_offset = int(
            (self.master.winfo_screenheight() / 2) - (self.master.winfo_height() / 2)
        )

        return (width_offset, height_offset)
Esempio n. 3
0
class GUIChecker(Frame):
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.master = master
        self.grid(columnspan=2000)

        self.__set_default_value()
        self.__create_widgets()
        self.__locate_widges()

        self.setting_on = False

    def __set_default_value(self):
        self.ALL_FIELDS = ["系號", "序號", "餘額", "課程名稱(連結課程地圖)",
                           "學分", "時間", "教師姓名*:主負責老師"
                           "已選課人數", "教室", "選必修", "限選條件",
                           "系所名稱", "年級", "組別", "類別", "班別",
                           "業界專家參與", "英語授課", "Moocs", "跨領域學分學程",
                           "備註", "課程碼", "分班碼", "屬性碼"]
        self.default_choosen_field = ["系號", "序號", "餘額", "課程名稱(連結課程地圖)",
                                      "學分", "時間", "教師姓名*:主負責老師"]
        self.choosen_field = deepcopy(self.default_choosen_field)

    def __create_widgets(self):
        self.input_label = Label(self, text="系所代碼 : ")
        self.input_field = Entry(self, width=30)
        self.search_btn = Button(self, text="搜尋", command=self.__search_method)
        self.clear_btn = Button(self, text="清除", command=self.__clear_method)
        self.setting_btn = Button(self, text="設定", command=self.__setting_method)
        self.msg_text = Label(self)

    def __locate_widges(self):
        self.input_label.grid(row=0, column=0)
        self.input_field.grid(row=0, column=1, columnspan=6)
        self.search_btn.grid(row=1, column=0)
        self.clear_btn.grid(row=1, column=2)
        self.setting_btn.grid(row=1, column=4)
        self.msg_text.grid(row=2, column=0)

    def __search_method(self):
        department_no = self.input_field.get()
        self.msg_text['text'] = '查詢中'
        try:
            self.__attach_course_table(department_no)
        except NoCourseAvailableError as e:
            logging.debug(e)
            self.msg_text['text'] = '沒有這個系所'
        except Exception as e:
            logging.exception('Seach Method: ')
            self.msg_text['text'] = '未知的錯誤'
        else:
            self.msg_text['text'] = ''

    def __attach_course_table(self, dept_no):
        courses = self.__search_courses(dept_no)
        title = list(courses.columns.values)

        self.__clear_method()
        self.__set_up_tree_widget(title, len(courses))

        for field in title:
            self.tree.heading(field, text=field)
            self.tree.column(field, width=font.Font().measure(field))

        for index, course in courses.iterrows():
            self.tree.insert('', 'end', values=tuple(course.values))
            for ix, val in enumerate(course.values):
                col_w = font.Font().measure(val) + 10
                if self.tree.column(title[ix], width=None) < col_w:
                    self.tree.column(title[ix], width=col_w)

    def __search_courses(self, dept_no):
        crawler = NckuCourseCrawler(dept_no=dept_no)
        html = crawler.get_raw_HTML()
        parser = NckuCourseParser(html)
        parser.include_fields = self.choosen_field
        logging.info("Choosen Field: {}".format(self.choosen_field))
        courses = parser.parse(sort=True)
        print(courses)
        courses['餘額'] = courses['餘額'].apply(int)
        return courses

    def __clear_method(self):
        try:
            self.__remove_tree_widget()
        except Exception as e:
            logging.debug(
                'Widget not yet created. Not and Error. {}'.format(e)
            )

    def __remove_tree_widget(self):
        self.tree_vsb.grid_remove()
        self.tree_hsb.grid_remove()
        self.tree.grid_remove()

    def __set_up_tree_widget(self, title, courses_num):
        tree_height = min(30, courses_num)
        self.tree = ttk.Treeview(columns=title, show="headings",
                                 height=tree_height)
        self.tree_vsb = ttk.Scrollbar(orient="vertical",
                                      command=self.tree.yview)
        self.tree_hsb = ttk.Scrollbar(orient="horizontal",
                                      command=self.tree.xview)
        self.tree.configure(yscrollcommand=self.tree_vsb.set,
                            xscrollcommand=self.tree_hsb.set)

        self.tree.grid(row=3, column=0)
        self.tree_vsb.grid(row=3, column=1, sticky="ns")
        self.tree_hsb.grid(row=4, column=0, sticky="we")

    def __setting_method(self):
        if not self.setting_on:
            self.setting_on = True

            self.setting_win = Toplevel(self)
            self.setting_win.wm_title("Settings")
            self.__create_setting_win_widget()

    def __create_setting_win_widget(self):
        choose_field_label = Label(self.setting_win, text="選擇欄位")
        self.__create_choose_field_listbox()
        confirm_btn = Button(self.setting_win, text="確定", command=self.__confirm_setting)
        default_btn = Button(self.setting_win, text="回復預設值", command=self.__restore_setting)

        confirm_btn.grid(row=0, column=0)
        default_btn.grid(row=0, column=1)
        choose_field_label.grid(row=1, column=0)

    def __create_choose_field_listbox(self):
        self.choose_field_listbox = Listbox(self.setting_win,
                                            selectmode=MULTIPLE,
                                            height=len(self.ALL_FIELDS))
        for i in self.ALL_FIELDS:
            self.choose_field_listbox.insert(END, i)
        for index, choosen in enumerate(self.choosen_field):
            if choosen:
                self.choose_field_listbox.select_set(index)

        self.choose_field_listbox.grid(row=1, column=1, sticky="nsew")

    def __confirm_setting(self):
        print(self.choose_field_listbox)
        print(self.choose_field_listbox.curselection())
        selections = self.choose_field_listbox.curselection()
        self.choosen_field = [
            self.ALL_FIELDS[col_index] for col_index in selections
        ]
        self.setting_win.withdraw()
        self.setting_on = False

    def __restore_setting(self):
        self.choose_field_listbox.grid_remove()
        self.choosen_field = self.default_choosen_field
        self.__create_choose_field_listbox()