├── README.md ├── YPT_Beta_UI.png ├── main.py ├── requirements.txt ├── ytpd_beta.py └── ytpd_beta.ui /README.md: -------------------------------------------------------------------------------- 1 | # YouTube Playlist Downloader 2 | Simple youtube playlist downloader. 3 | It's mostly meant to download music as it only downloads videos in a MP4 format. 4 | 5 | Beta version, there are still bugs. Loading bigger playlists may take a few minutes, around 3 minutes for a 200 song playlist. 6 | 7 | If something went wrong (program didn't work correctly) it could be due to an internal error, but is most likely due to: 8 | 9 | ### Possible Errors (Program not working correctly): 10 | #### Playlist didn't load up: 11 | - Playlist is set to private 12 | - Bad internet connection (it has to be really bad to not work) 13 | - Incorrect URL (it's not a YouTube playlist) 14 | - Unknown error 15 | 16 | #### If a certain video wasn't downloaded: 17 | - It was deleted from the playlist (no longer exists) 18 | - It was blocked by YouTube (could be any reason) 19 | - Blocked in country 20 | - Bad internet connection 21 | - Server connection error or unavailable format 22 | - Unknown error 23 | 24 | 25 | ## Guide 26 | ![UiPreview](https://github.com/15minutOdmora/YouTubePlaylistDownloader/blob/master/YPT_Beta_UI.png) 27 | 28 | - Paste/type in the URL of your playlist, check that the playlist is set to public, and then press Load. 29 | - Loading videos from playlist may take a while, depending on the size of the playlist. Around 5 seconds for a 10 song playlist. 30 | - You can only load one playlist and download the one. 31 | - Once the videos are loaded (they pop up in the box to the right), type in the name of the new folder in which it will be downloaded. 32 | - Click apply to change to the folder name (auto set to \YTPD_Beta) 33 | - Click Download to download music files, a loading bar will pop up. 34 | - This downloads all the files to the selected folder on Desktop (can not be changed). 35 | 36 | Use at your own risk, as downloading certain music/videos might be illegal due to copyrights. 37 | 38 | 39 | -------------------------------------------------------------------------------- /YPT_Beta_UI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/15minutOdmora/YouTubePlaylistDownloader/3eaf783b171ac5d55e96d903d9c8b5e281100c39/YPT_Beta_UI.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from pytube import YouTube, Playlist 3 | from PyQt5.QtCore import QThread, pyqtSignal 4 | from PyQt5.QtWidgets import * 5 | from ytpd_beta import UiMainWindow 6 | import os 7 | 8 | 9 | def seconds_to_mmss(seconds): 10 | """Function: 11 | Returns a string in the format of mm:ss""" 12 | min = seconds // 60 13 | sec = seconds % 60 14 | if min < 10: 15 | min_str = '0' + str(min) 16 | else: 17 | min_str = str(min) 18 | if sec < 10: 19 | sec_str = '0' + str(sec) 20 | else: 21 | sec_str = str(sec) 22 | return min_str + ":" + sec_str 23 | 24 | 25 | class DownloadingVideos(QThread): 26 | """ Download all videos from the videos_dict using the id, todo fix some bugs""" 27 | downloadCount = pyqtSignal(int, int, bool, str) # downloaded, number_of_videos, finished 28 | 29 | def __init__(self, videos_dict, download_path, parent=None): 30 | QThread.__init__(self, parent) 31 | self.videos_dict = videos_dict 32 | self.download_path = download_path 33 | 34 | def run(self): 35 | """ Main function, downloads videos by their id while emitting progress data""" 36 | # Create download folder before downloading 37 | if not os.path.isdir(self.download_path): # if path doesn't exist, create one. 38 | os.mkdir(self.download_path) 39 | # Download 40 | number_of_videos = len(self.videos_dict) 41 | failed_download = list() 42 | downloaded, now_downloading, finished = 0, "", False 43 | for key, value in self.videos_dict.items(): 44 | try: 45 | stream = value["stream"] 46 | print(key, ": stream=", stream) 47 | _ = stream.download(output_path=self.download_path) 48 | except: 49 | failed_download.append(key) 50 | print("Unable to download: ", key) 51 | downloaded += 1 52 | now_downloading = key 53 | if downloaded == number_of_videos: 54 | finished = True 55 | self.downloadCount.emit(downloaded, number_of_videos, finished, now_downloading) 56 | if len(failed_download) == 0: 57 | print("Download was successful, all videos downloaded.") 58 | else: 59 | print("Download wasn't as successfull, these videos weren't downloaded: ") 60 | for title in failed_download: 61 | print(title) 62 | 63 | 64 | class UrlLoading(QThread): 65 | """ Loads the videos data from playlist in another thread.""" 66 | countChanged = pyqtSignal(dict, bool) 67 | 68 | def __init__(self, playlist_link, parent=None): 69 | QThread.__init__(self, parent) 70 | self.playlist_link = playlist_link 71 | 72 | def run(self): 73 | """ Main function, gets all the playlist videos data, emits the info dict""" 74 | videos_dict = dict() 75 | try: 76 | print("Parsing playlist successful, loading video data...") 77 | playlist = Playlist(self.playlist_link) 78 | for video in playlist.videos: 79 | print(video.title, "... loaded") 80 | try: 81 | video.check_availability() 82 | except: # If video is unavailable skip, print error to console 83 | print("Unable to load: ", video.title) 84 | continue 85 | videos_dict[video.title] = dict() 86 | # Save stream object to directly download 87 | videos_dict[video.title]["stream"] = video.streams.filter(only_audio=True, file_extension='mp4').first() 88 | videos_dict[video.title]["duration"] = seconds_to_mmss(video.length) 89 | # Send data dict after each song is loaded 90 | self.countChanged.emit(videos_dict, True) 91 | except: 92 | print("Exception(Playlist/Video Error)... Something went wrong with loading playlist or one of the videos.") 93 | self.countChanged.emit({}, False) 94 | 95 | 96 | class MainPage(QMainWindow, UiMainWindow): 97 | def __init__(self, parent=None): 98 | super(MainPage, self).__init__(parent) 99 | self.setupUi(self) 100 | # Hide the fetching data label and the error label, shows up when its loading, invalid url 101 | self.url_fetching_data_label.hide() 102 | self.url_error_label.hide() 103 | # Seting the videos dict. and connecting the delete video button with the remove_selected_items fn. 104 | self.remove_from_list_button.clicked.connect(self.remove_selected_items) 105 | self.videos_dict = dict() 106 | # Hide progress bar, show it when it starts downloading 107 | self.progressBar.hide() 108 | # Buttons Connection with the appropriate functions 109 | self.url_load_button.clicked.connect(self.url_loading_button_click) 110 | self.download_button.clicked.connect(self.download_button_click) 111 | self.folder_name_button.clicked.connect(self.select_save_path) 112 | # Get the desktop path, set folder name, full download path, set label. 113 | self.current_path = os.path.dirname(os.path.abspath(__file__)) 114 | self.download_folder_name = "YTPD_beta" 115 | self.download_full_path = self.current_path + "\\" + self.download_folder_name 116 | self.download_path_label.setText("Download path: {}".format(self.download_full_path)) 117 | 118 | 119 | # Input url threading 120 | 121 | def url_loading_button_click(self): 122 | """ Reads input data from url_input and creates an instance of the UrlLoading thread """ 123 | self.listWidget.clear() # Clear the widget 124 | self.videos_dict = dict() # Clear the dict 125 | self.url_fetching_data_label.show() # Show the loading label 126 | self.url_error_label.hide() # Hide the error label if the input is a retry 127 | playlist_url = self.url_input.text() # Get the input text 128 | self.calc = UrlLoading(playlist_url) # Pass in the input text 129 | self.calc.countChanged.connect(self.url_loading_finished) # connect with the changing variables 130 | self.calc.start() 131 | 132 | def url_loading_finished(self, videos_dict, executed): 133 | """ Retrieves data from thread at the end, updates the list""" 134 | self.url_fetching_data_label.hide() # Hide the loading label as it has finished loading 135 | if executed: # If it was executed successfully 136 | videos_list, counter = list(), 0 137 | for key, value in videos_dict.items(): 138 | counter += 1 139 | line_str = str(counter) + ") " + key + " " + videos_dict[key]["duration"] # Display line 140 | videos_list.append(line_str) 141 | self.videos_dict = videos_dict 142 | self.listWidget.addItems(videos_list) # Update the list with the strings 143 | else: 144 | self.url_error_label.show() # Show the error label 145 | 146 | # Downloading videos threading 147 | 148 | def download_button_click(self): 149 | """ Executes when the button is clicked """ 150 | self.progressBar.show() 151 | self.download_button.setEnabled(False) 152 | self.down = DownloadingVideos(self.videos_dict, self.download_full_path) # Pass in the dict 153 | self.down.downloadCount.connect(self.downloading_update) # connect with the download function 154 | self.down.start() 155 | 156 | def downloading_update(self, downloaded, number_of_videos, finished, now_downloading): # int, bool 157 | """ Executes as it receives signals from thread """ 158 | # Update the progressBar, calculate the perc. here 159 | downloaded_percentages = int(round((downloaded / number_of_videos) * 100)) 160 | self.progressBar.setProperty("value", downloaded_percentages) 161 | # Changing the downloaded label 162 | if finished: 163 | self.download_button.setEnabled(True) 164 | self.progressBar.hide() 165 | self.downloaded_label.setText("Finished. {} files saved to:\n {}".format(downloaded, self.download_full_path)) 166 | self.progressBar.setProperty("value", 0) # reset loading bar 167 | else: 168 | self.downloaded_label.setText("{}\nDownloaded {} out of {}.".format(now_downloading, downloaded, 169 | number_of_videos)) 170 | 171 | # Items videos from videos(of playlist) list 172 | 173 | def remove_selected_items(self): 174 | """ Removes the selected items from the self.videos_dict and self.list_of_titles 175 | Also refreshes the listWidget""" 176 | list_items = self.listWidget.selectedItems() # list of the selected items (should not be a list but okay) 177 | selected_item_index = self.listWidget.currentRow() # index of the selected item 178 | if not list_items: # if nothing is selected 179 | return 180 | for item in list_items: # iterate through the list and delete the items 181 | self.listWidget.takeItem(self.listWidget.row(item)) 182 | 183 | if selected_item_index != -1: # -1 means nothing was selected 184 | index, delete_key = 0, '' 185 | for key, value in self.videos_dict.items(): 186 | if index == selected_item_index: 187 | delete_key = key # save the key of the deleted item in the videos_dict 188 | index += 1 189 | del self.videos_dict[delete_key] # delete the actual key 190 | 191 | # Getting input from the folder name folder_name_input, creating dir, changing labels 192 | 193 | def select_save_path(self): 194 | """ Sets the download path to the input one, auto set to YTPD_beta""" 195 | file = str(QFileDialog.getExistingDirectory(self, "Select Directory")) 196 | self.download_full_path = file 197 | self.download_path_label.setText("Download path: \n{}".format(self.download_full_path)) 198 | """self.download_folder_name = self.folder_name_input.text() 199 | self.download_full_path = self.current_path + "\\" + self.download_folder_name 200 | self.download_path_label.setText("Download path: \n{}".format(self.download_full_path))""" 201 | 202 | 203 | if __name__ == "__main__": 204 | app = QApplication(sys.argv) 205 | widget = MainPage() 206 | widget.show() 207 | sys.exit(app.exec_()) 208 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pytube3==9.6.4 2 | PyQt5==5.15.4 3 | pytube==10.7.2 4 | -------------------------------------------------------------------------------- /ytpd_beta.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore, QtGui, QtWidgets 2 | 3 | 4 | class UiMainWindow(object): 5 | """ GUI Design class """ 6 | def setupUi(self, MainWindow): 7 | """ GUI style and display settings""" 8 | MainWindow.setObjectName("MainWindow") 9 | MainWindow.resize(827, 498) 10 | font = QtGui.QFont() 11 | font.setBold(True) 12 | font.setUnderline(False) 13 | font.setWeight(75) 14 | MainWindow.setFont(font) 15 | self.centralwidget = QtWidgets.QWidget(MainWindow) 16 | self.centralwidget.setObjectName("centralwidget") 17 | # Main title 18 | self.title_label = QtWidgets.QLabel(self.centralwidget) 19 | self.title_label.setGeometry(QtCore.QRect(10, 0, 271, 41)) 20 | font = QtGui.QFont() 21 | font.setFamily("Segoe UI Emoji") 22 | font.setPointSize(14) 23 | font.setBold(True) 24 | font.setUnderline(False) 25 | font.setWeight(75) 26 | self.title_label.setFont(font) 27 | self.title_label.setObjectName("title_label") 28 | # Video title list display 29 | self.listWidget = QtWidgets.QListWidget(self.centralwidget) 30 | self.listWidget.setGeometry(QtCore.QRect(520, 40, 281, 301)) 31 | self.listWidget.setObjectName("listWidget") 32 | # URL input label 33 | self.enter_playlist_url_label = QtWidgets.QLabel(self.centralwidget) 34 | self.enter_playlist_url_label.setGeometry(QtCore.QRect(10, 70, 131, 21)) 35 | font = QtGui.QFont() 36 | font.setPointSize(10) 37 | font.setBold(True) 38 | font.setUnderline(False) 39 | font.setWeight(75) 40 | self.enter_playlist_url_label.setFont(font) 41 | self.enter_playlist_url_label.setObjectName("enter_playlist_url_label") 42 | # URL input box 43 | self.url_input = QtWidgets.QLineEdit(self.centralwidget) 44 | self.url_input.setGeometry(QtCore.QRect(140, 70, 301, 20)) 45 | self.url_input.setObjectName("url_input") 46 | # URL load button 47 | self.url_load_button = QtWidgets.QPushButton(self.centralwidget) 48 | self.url_load_button.setGeometry(QtCore.QRect(370, 100, 71, 23)) 49 | self.url_load_button.setObjectName("url_load_button") 50 | # Remove from QList buton 51 | self.remove_from_list_button = QtWidgets.QPushButton(self.centralwidget) 52 | self.remove_from_list_button.setGeometry(QtCore.QRect(670, 350, 131, 23)) 53 | self.remove_from_list_button.setObjectName("remove_from_list_button") 54 | # Loading bar 55 | self.progressBar = QtWidgets.QProgressBar(self.centralwidget) 56 | self.progressBar.setGeometry(QtCore.QRect(10, 390, 481, 16)) 57 | self.progressBar.setProperty("value", 0) 58 | self.progressBar.setObjectName("progressBar") 59 | # Error label 60 | self.url_error_label = QtWidgets.QLabel(self.centralwidget) 61 | self.url_error_label.setGeometry(QtCore.QRect(140, 50, 350, 16)) 62 | font = QtGui.QFont() 63 | font.setBold(False) 64 | font.setUnderline(False) 65 | font.setWeight(50) 66 | self.url_error_label.setFont(font) 67 | self.url_error_label.setObjectName("url_error_label") 68 | # Loading data label 69 | self.url_fetching_data_label = QtWidgets.QLabel(self.centralwidget) 70 | self.url_fetching_data_label.setGeometry(QtCore.QRect(140, 100, 231, 20)) 71 | font = QtGui.QFont() 72 | font.setBold(False) 73 | font.setUnderline(False) 74 | font.setWeight(50) 75 | self.url_fetching_data_label.setFont(font) 76 | self.url_fetching_data_label.setObjectName("url_fetching_data_label") 77 | # Download folder label 78 | """self.download_folder_name_label = QtWidgets.QLabel(self.centralwidget) 79 | self.download_folder_name_label.setGeometry(QtCore.QRect(10, 280, 161, 21)) 80 | font = QtGui.QFont() 81 | font.setPointSize(10) 82 | self.download_folder_name_label.setFont(font) 83 | self.download_folder_name_label.setObjectName("download_folder_name_label")""" 84 | # Folder name input box 85 | """self.folder_name_input = QtWidgets.QLineEdit(self.centralwidget) 86 | self.folder_name_input.setGeometry(QtCore.QRect(170, 280, 211, 20)) 87 | self.folder_name_input.setObjectName("folder_name_input")""" 88 | # Folder name load button 89 | self.folder_name_button = QtWidgets.QPushButton(self.centralwidget) 90 | self.folder_name_button.setGeometry(QtCore.QRect(390, 280, 100, 23)) 91 | self.folder_name_button.setObjectName("folder_name_button") 92 | # Download folder name input box 93 | self.download_path_label = QtWidgets.QLabel(self.centralwidget) 94 | self.download_path_label.setGeometry(QtCore.QRect(10, 295, 700, 30)) 95 | font = QtGui.QFont() 96 | font.setPointSize(9) 97 | font.setBold(True) 98 | font.setUnderline(False) 99 | # font.setWeight(50) 100 | self.download_path_label.setFont(font) 101 | self.download_path_label.setObjectName("download_path_label") 102 | # Description label 103 | self.guide_label = QtWidgets.QLabel(self.centralwidget) 104 | self.guide_label.setGeometry(QtCore.QRect(10, 130, 451, 111)) 105 | font = QtGui.QFont() 106 | font.setPointSize(8) 107 | font.setBold(False) 108 | font.setUnderline(False) 109 | font.setWeight(50) 110 | self.guide_label.setFont(font) 111 | self.guide_label.setObjectName("guide_label") 112 | # Lists widget label 113 | self.listWidget_label = QtWidgets.QLabel(self.centralwidget) 114 | self.listWidget_label.setGeometry(QtCore.QRect(520, 20, 201, 16)) 115 | font = QtGui.QFont() 116 | font.setBold(False) 117 | font.setUnderline(False) 118 | font.setWeight(50) 119 | self.listWidget_label.setFont(font) 120 | self.listWidget_label.setObjectName("listWidget_label") 121 | # Download button 122 | self.download_button = QtWidgets.QPushButton(self.centralwidget) 123 | self.download_button.setGeometry(QtCore.QRect(10, 350, 91, 23)) 124 | self.download_button.setObjectName("download_button") 125 | # Donload label 126 | self.downloaded_label = QtWidgets.QLabel(self.centralwidget) 127 | self.downloaded_label.setGeometry(QtCore.QRect(520, 390, 350, 30)) 128 | font = QtGui.QFont() 129 | font.setBold(False) 130 | font.setUnderline(False) 131 | font.setWeight(50) 132 | self.downloaded_label.setFont(font) 133 | self.downloaded_label.setObjectName("downloaded_label") 134 | # Bottom credits label 135 | self.credits_label = QtWidgets.QLabel(self.centralwidget) 136 | self.credits_label.setGeometry(QtCore.QRect(10, 460, 581, 16)) 137 | font = QtGui.QFont() 138 | font.setBold(False) 139 | font.setUnderline(False) 140 | font.setWeight(50) 141 | self.credits_label.setFont(font) 142 | self.credits_label.setObjectName("credits_label") 143 | # Bottom credits hyperlink 144 | self.link_label = QtWidgets.QLabel(self.centralwidget) 145 | self.link_label.setGeometry(QtCore.QRect(220, 460, 300, 16)) 146 | font = QtGui.QFont() 147 | font.setBold(False) 148 | font.setUnderline(True) 149 | font.setWeight(50) 150 | self.link_label.setFont(font) 151 | self.link_label.setOpenExternalLinks(True) 152 | self.link_label.setObjectName("link_label") 153 | # Main menu bar, not used 154 | MainWindow.setCentralWidget(self.centralwidget) 155 | self.menubar = QtWidgets.QMenuBar(MainWindow) 156 | self.menubar.setGeometry(QtCore.QRect(0, 0, 827, 21)) 157 | self.menubar.setObjectName("menubar") 158 | MainWindow.setMenuBar(self.menubar) 159 | self.statusbar = QtWidgets.QStatusBar(MainWindow) 160 | self.statusbar.setObjectName("statusbar") 161 | MainWindow.setStatusBar(self.statusbar) 162 | # Trainslations 163 | self.retranslateUi(MainWindow) 164 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 165 | 166 | def retranslateUi(self, MainWindow): 167 | """ Set the text and translations """ 168 | _translate = QtCore.QCoreApplication.translate 169 | MainWindow.setWindowTitle(_translate("MainWindow", "YTPD.Beta")) 170 | self.title_label.setText(_translate("MainWindow", "YouTube Playlist Downloader")) 171 | self.enter_playlist_url_label.setText(_translate("MainWindow", "Enter playlist url:")) 172 | self.url_load_button.setText(_translate("MainWindow", "Load")) 173 | self.remove_from_list_button.setText(_translate("MainWindow", "Remove from list")) 174 | self.url_error_label.setText(_translate("MainWindow", "Could not retrieve data from url." + 175 | " Check the link and try again?")) 176 | self.url_fetching_data_label.setText(_translate("MainWindow", "Fetching data ... This might take a while.")) 177 | self.download_folder_name_label.setText(_translate("MainWindow", "Download folder name:")) 178 | self.folder_name_button.setText(_translate("MainWindow", "Apply")) 179 | self.download_path_label.setText(_translate("MainWindow", "Download path: \n")) 180 | self.guide_label.setText(_translate("MainWindow", "Guide:\n" + 181 | "- Once the data from the url is loaded, video titles will be displayed" + 182 | " in the box to the right.\n" + 183 | "- By selecting a video title and pressing the Remove from list" + 184 | " button you can remove\n" + 185 | " the video from the download list.\n" + 186 | "- Then type in the folder name in which the files will be saved.\n" + 187 | "- The download path is set to the current path and" + 188 | " can not be changed.\n" + 189 | "- To download the files click Download. \n" + 190 | "- All video files are saved in a .mp4(only audio) format.")) 191 | self.listWidget_label.setText(_translate("MainWindow", "Playlist videos:")) 192 | self.download_button.setText(_translate("MainWindow", "Download")) 193 | self.downloaded_label.setText(_translate("MainWindow", "Waiting for download :)")) 194 | self.credits_label.setText(_translate("MainWindow", "Created by: L.M.," + 195 | " source files available at: ")) 196 | self.link_label.setText("'github.com/15minutOdmora/YouTubePlaylistDownloader'") 197 | 198 | 199 | if __name__ == "__main__": 200 | import sys 201 | app = QtWidgets.QApplication(sys.argv) 202 | MainWindow = QtWidgets.QMainWindow() 203 | ui = UiMainWindow() 204 | ui.setupUi(MainWindow) 205 | MainWindow.show() 206 | sys.exit(app.exec_()) 207 | -------------------------------------------------------------------------------- /ytpd_beta.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 827 10 | 498 11 | 12 | 13 | 14 | 15 | 75 16 | true 17 | false 18 | 19 | 20 | 21 | MainWindow 22 | 23 | 24 | 25 | 26 | 27 | 10 28 | 0 29 | 271 30 | 41 31 | 32 | 33 | 34 | 35 | Segoe UI Emoji 36 | 14 37 | 75 38 | true 39 | false 40 | 41 | 42 | 43 | YouTube Playlist Downloader 44 | 45 | 46 | 47 | 48 | 49 | 520 50 | 40 51 | 281 52 | 301 53 | 54 | 55 | 56 | 57 | 58 | 59 | 10 60 | 70 61 | 131 62 | 21 63 | 64 | 65 | 66 | 67 | 10 68 | 75 69 | true 70 | false 71 | 72 | 73 | 74 | Enter playlist url: 75 | 76 | 77 | 78 | 79 | 80 | 140 81 | 70 82 | 301 83 | 20 84 | 85 | 86 | 87 | 88 | 89 | 90 | 370 91 | 100 92 | 71 93 | 23 94 | 95 | 96 | 97 | Load 98 | 99 | 100 | 101 | 102 | 103 | 670 104 | 350 105 | 131 106 | 23 107 | 108 | 109 | 110 | Remove from list 111 | 112 | 113 | 114 | 115 | 116 | 10 117 | 390 118 | 481 119 | 16 120 | 121 | 122 | 123 | 24 124 | 125 | 126 | 127 | 128 | 129 | 140 130 | 50 131 | 271 132 | 16 133 | 134 | 135 | 136 | 137 | 50 138 | false 139 | false 140 | 141 | 142 | 143 | Could not retrieve data from url. Try again? 144 | 145 | 146 | 147 | 148 | 149 | 140 150 | 100 151 | 231 152 | 20 153 | 154 | 155 | 156 | 157 | 50 158 | false 159 | false 160 | 161 | 162 | 163 | Fetching data ... This might take a while. 164 | 165 | 166 | 167 | 168 | 169 | 10 170 | 280 171 | 161 172 | 21 173 | 174 | 175 | 176 | 177 | 10 178 | 179 | 180 | 181 | Download folder name: 182 | 183 | 184 | 185 | 186 | 187 | 170 188 | 280 189 | 211 190 | 20 191 | 192 | 193 | 194 | 195 | 196 | 197 | 390 198 | 280 199 | 61 200 | 23 201 | 202 | 203 | 204 | Apply 205 | 206 | 207 | 208 | 209 | 210 | 10 211 | 320 212 | 371 213 | 16 214 | 215 | 216 | 217 | 218 | 50 219 | false 220 | false 221 | 222 | 223 | 224 | Download path: c://neki/neki/neki 225 | 226 | 227 | 228 | 229 | 230 | 10 231 | 130 232 | 451 233 | 111 234 | 235 | 236 | 237 | 238 | 8 239 | 50 240 | false 241 | false 242 | 243 | 244 | 245 | Guide: 246 | - Once the data from the url is loaded, video titles will be displayed in the box to the right. 247 | - By selecting a video title and pressing the Remove from list button you can remove 248 | the vido from the download list. 249 | - Then type in the folder name in wich the files will be downloaded. 250 | - The download path is automaticly set to Desktop and can not be changed. 251 | - To download the files click Download. 252 | 253 | 254 | 255 | 256 | 257 | 520 258 | 20 259 | 201 260 | 16 261 | 262 | 263 | 264 | 265 | 50 266 | false 267 | false 268 | 269 | 270 | 271 | Playlist videos: 272 | 273 | 274 | 275 | 276 | 277 | 10 278 | 350 279 | 91 280 | 23 281 | 282 | 283 | 284 | Download 285 | 286 | 287 | 288 | 289 | 290 | 520 291 | 390 292 | 231 293 | 16 294 | 295 | 296 | 297 | 298 | 50 299 | false 300 | false 301 | 302 | 303 | 304 | Downloaded 0/max 305 | 306 | 307 | 308 | 309 | 310 | 10 311 | 420 312 | 581 313 | 16 314 | 315 | 316 | 317 | 318 | 50 319 | false 320 | false 321 | 322 | 323 | 324 | Created by: Liam Mislej, source files available at githublink 325 | 326 | 327 | 328 | 329 | 330 | 331 | 0 332 | 0 333 | 827 334 | 21 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | --------------------------------------------------------------------------------