├── ytdl ├── ui │ ├── __init__.py │ ├── pipe.py │ ├── about.py │ ├── settings.py │ └── window.py ├── update │ ├── __init__.py │ ├── updater.py │ └── _update_checker.py ├── extractor │ ├── __init__.py │ ├── description.py │ ├── thumbnail.py │ └── extractor.py ├── version.py ├── __main__.py ├── downloader │ ├── downloader.py │ └── matcher.py └── __init__.py ├── ytdl.cmd ├── logo.ico ├── logo.png ├── images ├── go.png ├── clear.png ├── clr2.png ├── dwred.png ├── gored.png ├── img0.png ├── img1.png ├── img10.png ├── img11.png ├── img12.png ├── img13.png ├── img14.png ├── img2.png ├── img3.png ├── img4.png ├── img6.png ├── img7.png ├── img8.png ├── img9.png ├── logo.ico ├── logo.png ├── paste.png ├── thred.png ├── browred.png ├── browse.png ├── clearred.png ├── paste2.png ├── pastered.png ├── settings.png ├── update.ico ├── aboutdark.png ├── donatedark.png ├── githubdark.png ├── updatedark.png ├── Frame 1newer.png ├── donatelight.png ├── optionsdark.png ├── settingsdark.png └── supportedwebsitesdark.png ├── screenshots ├── ytdl1.png ├── ytdl2.png ├── ytdl3.png ├── ytdl4.png ├── ytdl5.png ├── ytdl6.png ├── ytdl7.png ├── ytdl8.png ├── ytdl9.png └── ytdl10.png ├── ytdl.sh ├── requirements.txt ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── bug_report.yml │ ├── feature_request.yml │ └── question.yml └── FUNDING.yml ├── LICENSE └── README.md /ytdl/ui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ytdl/update/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ytdl/update/updater.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ytdl/extractor/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ytdl.cmd: -------------------------------------------------------------------------------- 1 | @py -bb -Werror -Xdev "%~dp0ytdl\__main__.py" %* -------------------------------------------------------------------------------- /logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/logo.ico -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/logo.png -------------------------------------------------------------------------------- /images/go.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/go.png -------------------------------------------------------------------------------- /ytdl/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '2023.08.29' 2 | 3 | VERSION_NAME = 'Apocalypse' -------------------------------------------------------------------------------- /images/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/clear.png -------------------------------------------------------------------------------- /images/clr2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/clr2.png -------------------------------------------------------------------------------- /images/dwred.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/dwred.png -------------------------------------------------------------------------------- /images/gored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/gored.png -------------------------------------------------------------------------------- /images/img0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/img0.png -------------------------------------------------------------------------------- /images/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/img1.png -------------------------------------------------------------------------------- /images/img10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/img10.png -------------------------------------------------------------------------------- /images/img11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/img11.png -------------------------------------------------------------------------------- /images/img12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/img12.png -------------------------------------------------------------------------------- /images/img13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/img13.png -------------------------------------------------------------------------------- /images/img14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/img14.png -------------------------------------------------------------------------------- /images/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/img2.png -------------------------------------------------------------------------------- /images/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/img3.png -------------------------------------------------------------------------------- /images/img4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/img4.png -------------------------------------------------------------------------------- /images/img6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/img6.png -------------------------------------------------------------------------------- /images/img7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/img7.png -------------------------------------------------------------------------------- /images/img8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/img8.png -------------------------------------------------------------------------------- /images/img9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/img9.png -------------------------------------------------------------------------------- /images/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/logo.ico -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/logo.png -------------------------------------------------------------------------------- /images/paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/paste.png -------------------------------------------------------------------------------- /images/thred.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/thred.png -------------------------------------------------------------------------------- /images/browred.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/browred.png -------------------------------------------------------------------------------- /images/browse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/browse.png -------------------------------------------------------------------------------- /images/clearred.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/clearred.png -------------------------------------------------------------------------------- /images/paste2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/paste2.png -------------------------------------------------------------------------------- /images/pastered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/pastered.png -------------------------------------------------------------------------------- /images/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/settings.png -------------------------------------------------------------------------------- /images/update.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/update.ico -------------------------------------------------------------------------------- /images/aboutdark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/aboutdark.png -------------------------------------------------------------------------------- /images/donatedark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/donatedark.png -------------------------------------------------------------------------------- /images/githubdark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/githubdark.png -------------------------------------------------------------------------------- /images/updatedark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/updatedark.png -------------------------------------------------------------------------------- /screenshots/ytdl1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/screenshots/ytdl1.png -------------------------------------------------------------------------------- /screenshots/ytdl2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/screenshots/ytdl2.png -------------------------------------------------------------------------------- /screenshots/ytdl3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/screenshots/ytdl3.png -------------------------------------------------------------------------------- /screenshots/ytdl4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/screenshots/ytdl4.png -------------------------------------------------------------------------------- /screenshots/ytdl5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/screenshots/ytdl5.png -------------------------------------------------------------------------------- /screenshots/ytdl6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/screenshots/ytdl6.png -------------------------------------------------------------------------------- /screenshots/ytdl7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/screenshots/ytdl7.png -------------------------------------------------------------------------------- /screenshots/ytdl8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/screenshots/ytdl8.png -------------------------------------------------------------------------------- /screenshots/ytdl9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/screenshots/ytdl9.png -------------------------------------------------------------------------------- /images/Frame 1newer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/Frame 1newer.png -------------------------------------------------------------------------------- /images/donatelight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/donatelight.png -------------------------------------------------------------------------------- /images/optionsdark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/optionsdark.png -------------------------------------------------------------------------------- /images/settingsdark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/settingsdark.png -------------------------------------------------------------------------------- /screenshots/ytdl10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/screenshots/ytdl10.png -------------------------------------------------------------------------------- /ytdl.sh: -------------------------------------------------------------------------------- 1 | exec "${PYTHON:-python3}" -bb -Werror -Xdev "$(dirname "$(realpath "$0")")/ytdl/__main__.py" "$@" -------------------------------------------------------------------------------- /images/supportedwebsitesdark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourabhkv/ytdl/HEAD/images/supportedwebsitesdark.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | mutagen 2 | pycryptodomex 3 | websockets 4 | brotli; platform_python_implementation=='CPython' 5 | brotlicffi; platform_python_implementation!='CPython' 6 | certifi 7 | Pillow 8 | requests 9 | pytube 10 | yt_dlp 11 | virtualenv -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Get help from the community on telegram, get the insights of ytdl 4 | url: https://t.me/ytdlgui 5 | about: Join the ytdl telegram for community-powered support! -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: sourabhkv # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | custom: [https://www.paypal.com/paypalme/PinakiSahu] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 5 | -------------------------------------------------------------------------------- /ytdl/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | __license__ = 'Public Domain' 4 | 5 | if __package__ is None: 6 | import os.path 7 | path = os.path.realpath(os.path.abspath(__file__)) 8 | sys.path.insert(0, os.path.dirname(os.path.dirname(path))) 9 | 10 | import ytdl 11 | 12 | if __name__=="__main__": 13 | ytdl.main(sys.argv) -------------------------------------------------------------------------------- /ytdl/update/_update_checker.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from ..version import __version__ 3 | from .updater import * 4 | 5 | 6 | def check_for_update(url: str): 7 | response = requests.get(url) 8 | data = response.json() 9 | latest_tag = data["tag_name"] 10 | print(latest_tag) 11 | if latest_tag > __version__: 12 | print("update") -------------------------------------------------------------------------------- /ytdl/ui/pipe.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from tkinter import messagebox 3 | from tkinter.ttk import * 4 | from tkinter import END 5 | 6 | class Pipe: 7 | 8 | def run_commandcustom(self,root,text_area,cmd): 9 | startupinfo = subprocess.STARTUPINFO() 10 | text_area.delete(0.0,END) 11 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 12 | root.title("Youtube-dl GUI [Executing command]") 13 | p = subprocess.Popen(cmd, startupinfo=startupinfo, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE,universal_newlines=True, bufsize=1) 14 | for line in iter(p.stdout.readline, b''): 15 | root.title("Youtube-dl GUI [Executing command]") 16 | text_area.insert(END,line[0:-1]+"\n") 17 | text_area.see(END) 18 | if str(line)=="": 19 | break 20 | 21 | p.stdout.close() 22 | p.wait() 23 | root.title("Youtube-dl GUI") 24 | messagebox.showinfo("Youtube-dl GUI","\nCommand Executed!") -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 sourabhkv 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ytdl/downloader/downloader.py: -------------------------------------------------------------------------------- 1 | import yt_dlp 2 | from datetime import timedelta 3 | from pprint import pprint 4 | 5 | class Downloader: 6 | def __init__(self,x) -> None: 7 | self.y = x 8 | 9 | def cli_to_api(self,opts): 10 | default = yt_dlp.parse_options([]).ydl_opts 11 | diff = {k: v for k, v in yt_dlp.parse_options(opts).ydl_opts.items() if default[k] != v} 12 | if 'postprocessors' in diff: 13 | diff['postprocessors'] = [pp for pp in diff['postprocessors'] if pp not in default['postprocessors']] 14 | 15 | myopts_opts = { 16 | 'logger': MyLogger(self.y), 17 | 'progress_hooks': [self.my_hook], 18 | } 19 | ydl_opts = {**myopts_opts, **diff} 20 | return ydl_opts 21 | 22 | def my_hook(self,d): 23 | try: 24 | if d['status'] == 'finished': 25 | self.y.status.set(' Done downloading, now post-processing ...') 26 | self.y.status.set(d['filename']) 27 | if d['status'] == 'downloading': 28 | self.y.status.set('[download] '+d['_default_template']) 29 | else: 30 | self.y.status.set(' Download complete') 31 | except: 32 | pass 33 | 34 | def start_download(self,ydl_opts): 35 | with yt_dlp.YoutubeDL(ydl_opts) as ydl: 36 | ydl.download([self.y.url_box.get()]) 37 | 38 | class MyLogger: 39 | def __init__(self,y) -> None: 40 | self.z = y 41 | 42 | def debug(self, msg): 43 | # For compatibility with youtube-dl, both debug and info are passed into debug 44 | # You can distinguish them by the prefix '[debug] ' 45 | if msg.startswith('[debug] '): 46 | pass 47 | else: 48 | self.info(msg) 49 | 50 | def info(self, msg): 51 | pass 52 | 53 | def warning(self, msg): 54 | pass 55 | 56 | def error(self, msg): 57 | self.z.status.set(' '+msg) -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report a bug unrelated to any particular site or extractor 3 | labels: [triage, bug] 4 | assignees: "sourabhkv,perrypinaki" 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE 9 | description: Fill all fields even if you think it is irrelevant for the issue 10 | options: 11 | - label: I understand that I will be **blocked** if I remove or skip any mandatory\* field 12 | required: true 13 | - type: checkboxes 14 | id: checklist 15 | attributes: 16 | label: Checklist 17 | description: | 18 | Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp: 19 | options: 20 | - label: I'm reporting a bug unrelated to a specific site 21 | required: true 22 | - label: I've verified that I'm running latest version of [ytdl](https://github.com/sourabhkv/ytdl/releases/latest/) ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit) 23 | required: true 24 | - label: I've checked that all provided URLs are playable in a browser with the same IP and same login details 25 | required: true 26 | - label: I've searched the [bugtracker](https://github.com/sourabhkv/ytdl/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates 27 | required: true 28 | - type: textarea 29 | id: description 30 | attributes: 31 | label: Provide a description that is worded well enough to be understood 32 | description: See [is-the-description-of-the-issue-itself-sufficient](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-description-of-the-issue-itself-sufficient) 33 | placeholder: Provide any additional information, any suggested solutions, and as much context and examples as possible 34 | validations: 35 | required: true 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Request a new functionality unrelated to any particular site or extractor 3 | labels: [triage, enhancement] 4 | assignees: "sourabhkv" 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE 9 | description: Fill all fields even if you think it is irrelevant for the issue 10 | options: 11 | - label: I understand that I will be **blocked** if I remove or skip any mandatory\* field & the issue will not be entertained 12 | required: true 13 | - type: checkboxes 14 | id: checklist 15 | attributes: 16 | label: Checklist 17 | description: | 18 | Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp: 19 | options: 20 | - label: I'm requesting a feature unrelated to a specific site 21 | required: true 22 | - label: I've looked through the [README](https://github.com/sourabhkv/ytdl#stream-selection) 23 | required: true 24 | - label: I've verified that I'm running latest version of ytdl ([update instructions](https://github.com/sourabhkv/ytdl#update-application)) or later (specify commit) 25 | required: true 26 | - label: I've searched the [bugtracker](https://github.com/sourabhkv/ytdl/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates 27 | required: true 28 | - type: textarea 29 | id: description 30 | attributes: 31 | label: Provide a description that is worded well enough to be understood 32 | description: See [is-the-description-of-the-issue-itself-sufficient](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-description-of-the-issue-itself-sufficient) 33 | placeholder: Provide any additional information, any suggested solutions, and as much context and examples as possible 34 | validations: 35 | required: true 36 | -------------------------------------------------------------------------------- /ytdl/extractor/description.py: -------------------------------------------------------------------------------- 1 | from tkinter import NORMAL, DISABLED 2 | class Description: 3 | def __init__(self,x) -> None: 4 | self.info = x.info 5 | pass 6 | 7 | def description_adder(self,x): 8 | if "youtube" in x.URL or "youtu.be" in x.URL: 9 | try: 10 | _length = str(self.info['duration_string']) 11 | _desc = str(self.info['description_string']) 12 | except: 13 | _length = '' 14 | _desc = '' 15 | 16 | _desc = str(self.info['description']) 17 | _title = str(self.info['title']) 18 | _channel = str(self.info['uploader']) 19 | _categories = str(self.info['categories'][0]) 20 | _views = str(self.info['view_count']) 21 | _pbdate = str(self.info['upload_date']) 22 | _pbdate=_pbdate[0:4]+"-"+_pbdate[4:6]+"-"+_pbdate[6:] 23 | _thumbnail = str(self.info['thumbnail']) 24 | try: 25 | self._data = _title + "\n\nDuration: " + _length + " | Channel: " +_channel + "\n\nPublished on: "+_pbdate + " | Views: "+_views + "\n\nCategory : " + _categories + "\n\nDescription\n"+_desc + "\n\nThumbnail\n"+_thumbnail 26 | 27 | except: 28 | pass 29 | 30 | else: 31 | _desc = '' 32 | _length = '' 33 | try: 34 | _title = str(self.info['title']) 35 | _length = str(self.info['duration_string']) 36 | _desc = str(self.info['description_string']) 37 | self._data = _title+'\n\nDuration: '+_length+'\n\nDescription:\n'+_desc 38 | except: 39 | self._data = _title+'\n\nDuration: '+_length+'\n\nDescription:\n'+_desc 40 | 41 | x.description_box.config(state=NORMAL) 42 | x.description_box.delete(1.0,"end") 43 | x.description_box.insert(1.0, self._data) 44 | x.description_box.configure(state=DISABLED) 45 | x.status.set('') -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yml: -------------------------------------------------------------------------------- 1 | name: Ask question 2 | description: Ask ytdl related question 3 | labels: [question] 4 | assignees: "sourabhkv,perrypinaki,ForeverAggregrate" 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE 9 | description: Fill all fields even if you think it is irrelevant for the issue 10 | options: 11 | - label: I understand that I will be **blocked** if I remove or skip any mandatory\* field 12 | required: true 13 | - type: markdown 14 | attributes: 15 | value: | 16 | ### Make sure you are **only** asking a question and not reporting a bug or requesting a feature. 17 | If your question contains "isn't working" or "can you add", this is most likely the wrong template. 18 | If you are in doubt whether this is the right template, **USE ANOTHER TEMPLATE**! 19 | - type: checkboxes 20 | id: checklist 21 | attributes: 22 | label: Checklist 23 | description: | 24 | Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp: 25 | options: 26 | - label: I'm asking a question and **not** reporting a bug or requesting a feature 27 | required: true 28 | - label: I've looked through the [README](https://github.com/sourabhkv/ytdl#readme) 29 | required: true 30 | - label: I've verified that I'm running latest version of [ytdl](https://github.com/sourabhkv/ytdl/releases/latest/) ([update instructions](https://github.com/sourabhkv/ytdl#update-application)) or later (specify commit) 31 | required: true 32 | - label: I've searched the [bugtracker](https://github.com/sourabhkv/ytdl/issues?q=) for similar questions **including closed ones**. DO NOT post duplicates 33 | required: true 34 | - type: textarea 35 | id: question 36 | attributes: 37 | label: Please make sure the question is worded well enough to be understood 38 | description: See [is-the-description-of-the-issue-itself-sufficient](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-description-of-the-issue-itself-sufficient) 39 | placeholder: Provide any additional information and as much context and examples as possible 40 | validations: 41 | required: true 42 | -------------------------------------------------------------------------------- /ytdl/ui/about.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import * 3 | from PIL import Image, ImageTk 4 | import webbrowser 5 | from ..version import __version__ 6 | 7 | class About: 8 | def __init__(self,sh,sw) -> None: 9 | root2 = tk.Toplevel() 10 | root2.focus_force() 11 | y=int((sh-500)/2)-30 12 | x=int((sw-600)/2) 13 | root2.geometry('%dx%d+%d+%d' % (600, 500, x, y)) 14 | root2.title('About Youtube-dl GUI') 15 | root2.configure(bg='#303135') 16 | image = Image.open("./images/logo.png") 17 | resize_image = image.resize((200, 200)) 18 | img = ImageTk.PhotoImage(resize_image) 19 | tk.Label(master=root2, text = "",image=img,bg="#303135",font=('Arial', 16),fg="white").place(relx=0.5, rely=0.25,anchor= CENTER) 20 | Label(root2, text = "Youtube-dl GUI v"+__version__,bg="#303135",font=('Arial', 12),fg="white").place(relx=.5, rely=.5,anchor= CENTER) 21 | Label(root2, text = "Released under MIT License",bg="#303135",fg="white").place(relx=.5, rely=.56,anchor= CENTER) 22 | Label(root2, text = "This is project is based on yt-dlp , ffmpeg , atomic parsley",bg="#303135",fg="white").place(relx=.5, rely=.69,anchor= CENTER) 23 | Label(root2, text = "THIS IS ONLY FOR EDUCATIONAL PURPOSE.",font=('Arial', 9,'bold'),fg="red",bg="#303135").place(relx=.5, rely=.75,anchor= CENTER) 24 | name74 = Label(root2, text = "CREDITS :",bg="#303135",fg="white").place(x=40+70,y=290) 25 | name74 = Label(root2, text = "yt-dlp",bg="#303135",fg="#0574FF",cursor="hand2") 26 | name74.place(x = 135+85,y = 290) 27 | name74.bind("", lambda e: webbrowser.open('https://github.com/yt-dlp/yt-dlp')) 28 | name73 = Label(root2, text = "ffmpeg",fg="#0574FF",cursor="hand2",bg="#303135") 29 | name73.place(x = 210+95,y = 290) 30 | name73.bind("", lambda e: webbrowser.open('https://www.ffmpeg.org/')) 31 | name75 = Label(root2, text = "AtomicParsley",fg="#0574FF",cursor="hand2",bg="#303135") 32 | name75.place(x = 290+105,y = 290) 33 | name75.bind("", lambda e: webbrowser.open('http://atomicparsley.sourceforge.net/')) 34 | name76 = Label(root2, text = "TELEGRAM",fg="orange",cursor="hand2",bg="#303135") 35 | name76.place(x = 190+145,y = 390) 36 | name76.bind("", lambda e: webbrowser.open('https://t.me/ytdlgui')) 37 | name77 = Label(root2, text = "DONATE",fg="yellow",cursor="hand2",bg="#303135") 38 | name77.place(x = 190,y = 390) 39 | name77.bind("", lambda e: webbrowser.open('https://github.com/sourabhkv/ytdl#support-us')) 40 | name7e = Label(root2, text = "Changelog",fg="#0574FF",cursor="hand2",bg="#303135") 41 | name7e.bind("", lambda e: webbrowser.open('https://github.com/sourabhkv/ytdl/releases/tag/v23.0305.12')) 42 | name7e.place(x = 195,y = 315) 43 | name8e = Label(root2, text = "Check for updates",fg="#0574FF",cursor="hand2",bg="#303135") 44 | #name8e.bind("", lambda e: os.startfile("updater.exe")) 45 | name8e.place(x = 295,y = 315) 46 | streams2 = Label(root2, text = "https://github.com/sourabhkv/ytdl",bg="#303135",fg="white").place(relx=.5, rely=.87,anchor= CENTER) 47 | streams2 = Label(root2, text = "Developed by sourabhkv",bg="#303135",fg="green").place(relx=.5, rely=.93,anchor= CENTER) 48 | #webbrowser.open final2 https://drive.google.com/file/d/1CWW5YTK7MjIQ3ZyQdsN7TSNu_K-F3hxN/view?usp=sharing 49 | root2.resizable(False, False) 50 | root2.iconbitmap(r'./images/logo.ico') 51 | root2.mainloop() -------------------------------------------------------------------------------- /ytdl/ui/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from tkinter import * 3 | import tkinter as tk 4 | from tkinter import ttk 5 | import webbrowser 6 | from tkinter import messagebox 7 | 8 | class Settings: 9 | def Settings_page(self,sh,sw): 10 | self.root = tk.Tk() 11 | self.root.configure(bg='#303135') 12 | self.root.focus_force() 13 | y=int((sh-500)/2)-30 14 | x=int((sw-500)/2) 15 | self.root.geometry('%dx%d+%d+%d' % (500, 500, x, y)) 16 | self.root.title('Youtube-dl GUI Settings') 17 | Label(self.root, text = "Settings",font=('Arial', 16),bg="#303135",fg="white").place(relx=0.5, rely=0.05,anchor= CENTER) 18 | Label(self.root, text = "Default Download Options",bg="#303135",fg="white").place(relx=0.5, rely=0.12,anchor= CENTER) 19 | Label(self.root, text = "Path to Cookie file",bg="#303135",fg="white").place(relx=0.5, rely=0.28,anchor= CENTER) 20 | Label(self.root, text = "Output template for video",bg="#303135",fg="white").place(relx=0.5, rely=0.44,anchor= CENTER) 21 | Label(self.root, text = "Output template for playlist",bg="#303135",fg="white").place(relx=0.5, rely=0.56,anchor= CENTER) 22 | dd=Label(self.root, text = "Update yt-dlp",fg="#0574FF",cursor="hand2",bg="#303135") 23 | dd.place(x = 390,y = 460) 24 | #dd.bind("", lambda e: updateback()) 25 | name75 = Label(self.root, text = "Report issue",fg="#0574FF",cursor="hand2",bg="#303135") 26 | name75.place(x = 15,y = 460) 27 | name75.bind("", lambda e: webbrowser.open('https://github.com/sourabhkv/ytdl/issues')) 28 | 29 | self.optnentry=ttk.Entry(self.root,width=74) 30 | self.cookiepath=ttk.Entry(self.root,width=71) 31 | self.out_vid=ttk.Entry(self.root,width=74) 32 | self.out_plst=ttk.Entry(self.root,width=74) 33 | 34 | #self.x = ttk.Button(self.root, text ="Save", command = self.save()) 35 | #self.x.place(relx=0.5, rely=0.8,anchor= CENTER) 36 | file2=open("./config/cookies.txt",'r') 37 | data1=file2.readlines() 38 | file2.close() 39 | file3=open("./config/args.txt",'r') 40 | data2=file3.readlines() 41 | file3.close() 42 | file4=open("./config/output_temp_vid.txt",'r') 43 | out_temp_vid=file4.readlines() 44 | file4.close() 45 | file5=open("./config/output_temp_playlist.txt",'r') 46 | out_temp_plst=file5.readlines() 47 | file5.close() 48 | 49 | if len(data2)!=0: 50 | self.optnentry.insert(0,data2[0]) 51 | elif len(data1)!=0: 52 | self.cookiepath.insert(0,data1[0]) 53 | 54 | self.out_plst.insert(0,out_temp_plst[0]) 55 | self.out_plst.place(x=20,y=300) 56 | self.out_vid.insert(0,out_temp_vid[0]) 57 | self.out_vid.place(x=20,y=240) 58 | 59 | self.optnentry.place(x=20,y=78) 60 | #cookiepath.insert(0,data3) 61 | self.cookiepath.place(x=20,y=158) 62 | Button(self.root, text =".", command = self.cookieselect()).place(x=455,y=156) 63 | self.root.resizable(False, False) 64 | self.root.iconbitmap(r'./images/logo.ico') 65 | self.root.mainloop() 66 | 67 | def save(self): 68 | c1=self.optnentry.get() 69 | c2=self.cookiepath.get() 70 | c3=self.out_vid.get() 71 | c4=self.out_plst.get() 72 | with open("./config/cookies.txt",'w+') as file: 73 | file.write(c2) 74 | 75 | with open("./config/args.txt",'w+') as file: 76 | file.write(c1) 77 | 78 | with open("./config/output_temp_vid.txt",'w+') as file: 79 | file.write(c3) 80 | 81 | with open("./config/output_temp_playlist.txt",'w+') as file: 82 | file.write(c4) 83 | 84 | #root4.destroy() 85 | self.root.destroy() 86 | messagebox.showinfo("Youtube-dl GUI", "Settings saved!") -------------------------------------------------------------------------------- /ytdl/extractor/thumbnail.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from io import BytesIO 3 | from PIL import ImageTk,Image 4 | from tkinter import ttk, Label, StringVar 5 | from threading import Thread 6 | class Search_thumbnail: 7 | def search_thumbnail(self,x): 8 | info = x.info 9 | url = x.URL 10 | if "youtube" in url and "music" not in url and "shorts" not in url or "youtu.be" in url and "music" not in url and "shorts" not in url: 11 | if "youtube" in url: 12 | d=url.find("=") 13 | vid=url[d+1:] 14 | elif "youtu.be" in url: 15 | vid=url[url.rfind("/")+1:] 16 | thurl = "https://i.ytimg.com/vi/{}/mqdefault.jpg".format(vid) 17 | r = requests.get(thurl) 18 | img = Image.open(BytesIO(r.content)) 19 | img.mode = 'RGB' 20 | image = img.resize((270, 160), Image.LANCZOS) 21 | x.thimg = ImageTk.PhotoImage(image) 22 | Label(x.root, image=x.thimg) 23 | picbtn=ttk.Button(x.root, text = 'thumbnail', image = x.thimg,command=lambda : self.viewer(img)) 24 | picbtn.place(x=710,y=40) 25 | 26 | elif "youtube" in url and "music" in url: 27 | if url.find("&")==-1: 28 | vid=url[1+url.find("="):] 29 | else: 30 | vid=url[1+url.find("="):url.find("&")] 31 | #print(vid) 32 | thurl = "https://i.ytimg.com/vi/{}/mqdefault.jpg".format(vid) 33 | r = requests.get(thurl) 34 | img = Image.open(BytesIO(r.content)) 35 | img.mode = 'RGB' 36 | image = img.resize((270, 160), Image.LANCZOS) 37 | x.thimg = ImageTk.PhotoImage(image)#https://www.youtube.com/shorts/WSFe3Rp7arQ 38 | Label(x.root, image=x.thimg) 39 | picbtn=ttk.Button(x.root, text = 'thumbnail', image = x.thimg,command=lambda : self.viewer(img)) 40 | picbtn.place(x=710,y=40) 41 | 42 | elif "youtube" in url and "shorts" in url: 43 | #print("ghfghgfhf") 44 | vid=url.rstrip("?feature=share")[url.rstrip("?feature=share").rfind("/")+1:] 45 | #print(vid) 46 | #print(vid,url) 47 | thurl = "https://i.ytimg.com/vi/{}/mqdefault.jpg".format(vid) 48 | r = requests.get(thurl) 49 | img = Image.open(BytesIO(r.content)) 50 | img.mode = 'RGB' 51 | image = img.resize((270, 160), Image.LANCZOS) 52 | x.thimg = ImageTk.PhotoImage(image)#https://www.youtube.com/shorts/WSFe3Rp7arQ 53 | Label(x.root, image=x.thimg) 54 | picbtn=ttk.Button(x.root, text = 'thumbnail', image = x.thimg,command=lambda : self.viewer(img))#work left in viewer image not showing up 55 | picbtn.place(x=710,y=40) 56 | 57 | else: 58 | if 'thumbnail' in info: 59 | thurl=info['thumbnail'] 60 | try: 61 | r = requests.get(thurl) 62 | img = Image.open(BytesIO(r.content)) 63 | width, height = img.size 64 | i=2 65 | while (i)>0: 66 | if (i*height)<=170 and (i*width)<=270: 67 | break 68 | else: 69 | i=i-0.02 70 | nw=int(i*width) 71 | nh=int(i*height) 72 | sp=int((230-nw)/2) 73 | #print(nw,nh) 74 | image = img.resize(((nw),(nh)), Image.Resampling.LANCZOS) 75 | x.thimg = ImageTk.PhotoImage(image) 76 | Label(x.root, image=x.thimg) 77 | picbtn=ttk.Button(x.root, text = 'thumbnail', image = x.thimg,command=lambda : self.viewer(img)) 78 | picbtn.place(x=sp+730,y=40) 79 | 80 | except: 81 | x.na = StringVar() 82 | x.na.set("Thumbnail unavailable") 83 | x.thumbnail_unavailable_label = Label(x.root, textvariable = x.na,bg="#525252",fg="white") 84 | x.thumbnail_unavailable_label.place(x = 780,y = 70) 85 | 86 | def viewer(self,img): 87 | t1w = Thread(target=self.viewers, args=(img,)) 88 | t1w.start() 89 | 90 | def viewers(self,img): 91 | img.show() -------------------------------------------------------------------------------- /ytdl/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from .ui import window 3 | from .update._update_checker import check_for_update 4 | from threading import Thread 5 | from tkinter import * 6 | from tkinter import ttk 7 | import requests 8 | import zipfile 9 | 10 | __license__ = 'Public Domain' 11 | 12 | class maker: 13 | 14 | 'This class is to check if all files exist, if not create it' 15 | 16 | def __init__(self) -> None: 17 | global _dependency_status 18 | _dependency_status = False 19 | if os.path.exists("./config"): 20 | pass 21 | else: 22 | os.makedirs("./config",exist_ok=False) 23 | with open("./config/loc.txt",'w') as file: 24 | file.write((os.path.expanduser('~')+"\\Downloads").replace("\\","/")) 25 | 26 | with open("./config/history.txt",'w') as file1: 27 | with open("./config/log.txt",'w') as file2: 28 | with open("./config/cookies.txt",'w') as file3: 29 | with open("./config/args.txt",'w') as file4: 30 | with open("./config/loc.txt",'w') as file5: 31 | file5.write((os.path.expanduser('~')+"\\Downloads").replace("\\","/")) 32 | 33 | with open("./config/output_temp_vid.txt",'w') as file: 34 | file.write("%(title)s.%(ext)s") 35 | 36 | with open("./config/output_temp_playlist.txt",'w') as file: 37 | file.write("%(playlist_title)s %(playlist_index)s %(title)s.%(ext)s") 38 | 39 | def download_dependency(self): 40 | global _dependency_status 41 | _files_list = os.listdir() 42 | if "yt-dlp_86.exe" not in _files_list and "ffmpeg.exe" not in _files_list and "AtomicParsley.exe" not in _files_list: 43 | self.dependency_status = False 44 | self._root = Tk() 45 | self.screen_width = self._root.winfo_screenwidth() 46 | self.screen_height = self._root.winfo_screenheight() 47 | _y=int((self.screen_height-340)/2) 48 | _x=int((self.screen_width-500)/2) 49 | self._root.geometry('%dx%d+%d+%d' % (500, 300, _x, _y)) 50 | self._root.title('YouTube-dl GUI Dependency installer') 51 | Label(self._root,text="Youtube-dl GUI needs to install the following dependency\n\n1.yt-dlp\n\n2.ffmpeg\n\n3.AtomicParsley").place(x=100,y=10) 52 | self._download_status = StringVar() 53 | self._download_status.set('Installation will take approx 30 Mb internet and 43 Mb of disk space') 54 | Label(self._root,textvariable=self._download_status).place(x=20,y=210) 55 | self.install_btn = ttk.Button(self._root,text='Install dependency',command=self.disable_btn) 56 | self.install_btn.place(x=200,y=160) 57 | self.progress = ttk.Progressbar(self._root, orient="horizontal", length=450, mode="determinate") 58 | self.progress.place(x=20,y=250) 59 | self._root.iconbitmap('./images/logo.ico') 60 | self._root.mainloop() 61 | 62 | else: 63 | _dependency_status = True 64 | 65 | def disable_btn(self): 66 | self.install_btn.config(state=DISABLED) 67 | download_thread = Thread(target=self.download_file) 68 | download_thread.start() 69 | 70 | def download_file(self): 71 | global _dependency_status 72 | url1 = 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_x86.exe' 73 | response = requests.get(url1, stream=True) 74 | total_size = int(response.headers.get('content-length', 0)) 75 | block_size = 1024 76 | self._download_status.set('[downloading yt-dlp . . .]') 77 | with open('yt-dlp_x86.exe', 'wb') as file: 78 | for data in response.iter_content(block_size): 79 | file.write(data) 80 | self.progress["value"] += len(data) 81 | 82 | url2 = 'https://github.com/ForeverAggregrate/ytdl_dependency/releases/latest/download/Dependency.zip' 83 | response = requests.get(url2, stream=True) 84 | total_size = int(response.headers.get('content-length', 0)) 85 | block_size = 1024 86 | self._download_status.set('[downloading ffmpeg,AtomicParsley . . .]') 87 | with open('Dependency.zip', 'wb') as file: 88 | for data in response.iter_content(block_size): 89 | file.write(data) 90 | self.progress["value"] += len(data) 91 | 92 | self.progress["value"] = 0 93 | 94 | self._download_status.set('[extracting files . . .]') 95 | with zipfile.ZipFile('Dependency.zip', 'r') as zip: 96 | zip.extractall('./') 97 | 98 | os.remove('Dependency.zip') 99 | self._download_status.set('[Done] All dependency is installed ,Restart the program') 100 | _dependency_status = True 101 | 102 | 103 | cleaner = maker() 104 | cleaner.download_dependency() 105 | 106 | 107 | #mythread = Thread(target=check_for_update,args=("https://api.github.com/repos/sourabhkv/ytdl/releases/latest",)) 108 | #mythread.start() 109 | 110 | def main(args=None): 111 | if _dependency_status: 112 | window.Window(args).root.mainloop() -------------------------------------------------------------------------------- /ytdl/downloader/matcher.py: -------------------------------------------------------------------------------- 1 | from tkinter import messagebox 2 | from .downloader import Downloader 3 | from threading import Thread 4 | import os 5 | 6 | class Match_case: 7 | def __init__(self) -> None: 8 | self.my_opts = [ 9 | '--no-mtime', 10 | '--no-restrict-filenames', 11 | '--add-metadata', 12 | '--color','no_color', 13 | ] 14 | 15 | def checking(self, x): 16 | #tab1 check 17 | if x.basic_combobox.get() or x.music_combobox.get(): 18 | if x.basic_combobox.get(): 19 | print(x.basic_combobox.get()) 20 | __temp = [ 21 | '-f '+ x.basic_combobox.get().split()[0], 22 | ] 23 | self.my_opts.extend(__temp) 24 | else: 25 | print(x.music_combobox.get()) 26 | 27 | __temp = [ 28 | '--embed-thumbnail', 29 | '--parse-metadata','description:(?s)(?P.+)', 30 | '--parse-metadata','album:(?s)(?P.+)', 31 | '--parse-metadata','genre:(?s)(?P.+)', 32 | '--parse-metadata','uploader:(?s)(?P.+)', 33 | '--parse-metadata','%(upload_date,release_year).4s:(?P.+)', 34 | ] 35 | if "Mp3" in x.music_combobox.get(): 36 | if '64' in x.music_combobox.get(): 37 | __temp.extend(['-x','--audio-format','mp3','--audio-quality','64K','-f wa']) 38 | if '128' in x.music_combobox.get(): 39 | __temp.extend(['-x','--audio-format','mp3','--audio-quality','128K','-f ba']) 40 | if '320' in x.music_combobox.get(): 41 | __temp.extend(['-x','--audio-format','mp3','--audio-quality','320K','-f ba']) 42 | elif "M4a" in x.music_combobox.get(): 43 | __temp.extend(['-x','--audio-format','m4a','--audio-quality','0','-f ba']) 44 | elif "Wav" in x.music_combobox.get(): 45 | __temp.extend(['-x','--audio-format','wav','--audio-quality','0','-f ba']) 46 | elif "Flac" in x.music_combobox.get(): 47 | __temp.extend(['-x','--audio-format','flac','--audio-quality','0','-f ba']) 48 | 49 | self.my_opts.extend(__temp) 50 | 51 | 52 | #tab2 check 53 | elif x.video_streams_combobox.get() or x.audio_streams_combobox.get() or x.captions_combobox.get(): 54 | print(x.video_streams_combobox.get() , x.audio_streams_combobox.get() , x.captions_combobox.get()) 55 | if x.video_streams_combobox.get() and x.audio_streams_combobox.get(): 56 | __temp = [ 57 | '-f ' + x.audio_streams_combobox.get().split()[0] + "+" + x.video_streams_combobox.get().split()[0], 58 | ] 59 | self.my_opts.extend(__temp) 60 | elif x.video_streams_combobox.get() and x.audio_streams_combobox.get() and x.captions_combobox.get(): 61 | __temp = [ 62 | '-f ' + x.audio_streams_combobox.get().split()[0] + "+" + x.video_streams_combobox.get().split()[0],\ 63 | '--write-srt --sub-lang {}'.format(x.captions_combobox.get().split()[0]), 64 | ] 65 | self.my_opts.extend(__temp) 66 | elif x.video_streams_combobox.get(): 67 | __temp = [ 68 | '-f ' + x.video_streams_combobox.get().split()[0], 69 | ] 70 | self.my_opts.extend(__temp) 71 | 72 | else: 73 | messagebox.showerror("Youtube-dl GUI","Select valid combination") 74 | return 75 | 76 | if x.embdth_var.get(): 77 | self.my_opts.append('--embed-thumbnail') 78 | if x.ratelim_entry.get(): 79 | self.my_opts.append('-r '+x.ratelim_entry.get()) 80 | 81 | if x.customfile_entry.get(): 82 | _user_path = os.path.expanduser('~').replace('\\','/') 83 | if _user_path in x.download_Directory: 84 | x.download_Directory = '/'+x.download_Directory.lstrip( _user_path ) 85 | if x.download_Directory[-1]=='/': 86 | x.download_Directory = x.download_Directory[:-1] 87 | _temp_loc = ['-o','~'+x.download_Directory + '/' + x.customfile_entry.get() + '.%(ext)s'] 88 | self.my_opts.extend(_temp_loc) 89 | else: 90 | _user_path = os.path.expanduser('~').replace('\\','/') 91 | if _user_path in x.download_Directory: 92 | x.download_Directory = '/'+x.download_Directory.lstrip( _user_path ) 93 | if x.download_Directory[-1]=='/': 94 | x.download_Directory = x.download_Directory[:-1] 95 | _temp_loc = ['-o','~'+x.download_Directory + '/'+'%(title)s.%(ext)s'] 96 | self.my_opts.extend(_temp_loc) 97 | 98 | if x.proxy_entry.get(): 99 | self.my_opts.append('--proxy '+x.proxy_entry.get()) 100 | if x.format_checkbox.get()!='auto-detect': 101 | self.my_opts.append('--merge-output-format '+x.format_checkbox.get()) 102 | 103 | dl = Downloader(x) 104 | print(self.my_opts) 105 | self.my_opts = tuple(self.my_opts) 106 | _res = dl.cli_to_api(self.my_opts) 107 | print(_res) 108 | _mythread = Thread(target=dl.start_download , args=(_res,)) 109 | _mythread.start() -------------------------------------------------------------------------------- /ytdl/extractor/extractor.py: -------------------------------------------------------------------------------- 1 | from yt_dlp import YoutubeDL 2 | from tkinter import messagebox 3 | from .thumbnail import Search_thumbnail 4 | from .description import Description 5 | from threading import Thread 6 | 7 | class extract_info: 8 | global x 9 | def __init__(self,x) -> None: 10 | if len(x.url_box.get())==0: 11 | messagebox.showerror("Youtube-dl GUI"," Enter Valid URL ") 12 | else: 13 | self.url = x.url_box.get() 14 | x.URL = self.url 15 | self.merged_list = [] 16 | self.audio_list = [] 17 | self.video_list = [] 18 | self.subs=[] 19 | 20 | def search(self,x): 21 | ydl_opts = {} 22 | with YoutubeDL(ydl_opts) as ydl: 23 | info = ydl.extract_info(self.url, download=False) 24 | x.info = info 25 | print(len(info['formats'])) 26 | 27 | temp1 = Search_thumbnail() 28 | temp_thread1 = Thread(target= temp1.search_thumbnail ,args= ( x,)) 29 | temp_thread1.start() 30 | 31 | temp2 = Description(x) 32 | temp_thread2 = Thread(target= temp2.description_adder , args= ( x,)) 33 | temp_thread2.start() 34 | 35 | master = { 36 | 'format_id':[], 37 | 'format_note':[], 38 | 'filesize':[], 39 | 'ext':[], 40 | 'acodec':[], 41 | 'vcodec':[], 42 | 'resolution':[], 43 | 'tbr':[], 44 | 'fps':[], 45 | 'dynamic_range':[], 46 | } 47 | 48 | tasker = { 49 | 'format_id':0, 50 | 'format_note':1, 51 | 'filesize':2, 52 | 'ext':3, 53 | 'acodec':4, 54 | 'vcodec':5, 55 | 'resolution':6, 56 | 'tbr':7, 57 | 'fps':8, 58 | 'dynamic_range':9, 59 | } 60 | for i in range(len(info['formats'])): 61 | _x = info['formats'][i].keys() 62 | t = [None]*10 63 | for j in _x: 64 | if str(j) in tasker: 65 | t[tasker[j]] = info['formats'][i][j] 66 | elif 'filesize_approx' in str(j): 67 | print("apppp") 68 | t[tasker['filesize']] = info['formats'][i][j] 69 | elif not t[tasker['format_note']] and str(j)=='height': 70 | t[tasker['format_note']] = str(info['formats'][i]['height']) + 'p' 71 | 72 | #print(t) 73 | counter = 0 74 | for k in master: 75 | master[k].append(t[counter]) 76 | counter+=1 77 | _temp = "" 78 | 79 | 80 | if len(t)>0 and t[3]!='mhtml': 81 | #print(t) 82 | if t[4]=='none': 83 | t[4]=None 84 | if t[5]=='none': 85 | t[5]=None 86 | _temp="" 87 | if t[0] is None: 88 | continue 89 | #print(t[4],t[5],type(t[4]),type(t[5]),"afeter",t[4] is None,t[5] is None) 90 | if (t[4] is None and t[5] is not None) or (t[4] is None and t[5] is None):#only video 91 | if t[0] and t[0]!='none': 92 | _temp = t[0] 93 | if t[1] and t[1]!='none': 94 | _temp +=" | " +t[1] 95 | if t[2] and t[2]!='none': 96 | _temp += " | Size: " +self.size(t[2]) 97 | if t[3] and t[3]!='none': 98 | _temp += " | Ext: "+t[3] 99 | if t[5] and t[5]!='none': 100 | _temp += " | "+t[5] 101 | if t[6] and t[6]!='none': 102 | _temp += " | "+t[6] 103 | if t[7] and t[7]!='none': 104 | _temp += " | Bitrate: "+str(round(t[7],2))+" K/s" 105 | if t[8] and t[8]!='none': 106 | _temp += " | FPS: "+str(t[8]) 107 | if t[9] and t[9]!='none': 108 | _temp += " | "+t[9] 109 | 110 | self.video_list.append(_temp) 111 | 112 | if t[5] is None and t[4] is not None:# only audio 113 | if t[0] and t[0]!='none': 114 | _temp = t[0] 115 | if t[1] and t[1]!='none': 116 | _temp +=" | " +t[1] 117 | if t[2] and t[2]!='none': 118 | _temp += " | Size: " +self.size(t[2]) 119 | if t[3] and t[3]!='none': 120 | _temp += " | Ext: "+t[3] 121 | if t[4] and t[4]!='none': 122 | _temp += " | "+t[4] 123 | if t[5] and t[5]!='none': 124 | _temp += " | "+t[5] 125 | if t[6] and t[6]!='none': 126 | _temp += " | "+t[6] 127 | if t[7] and t[7]!='none': 128 | _temp += " | Bitrate: "+str(round(t[7],2))+" K/s" 129 | 130 | self.audio_list.append(_temp) 131 | 132 | if t[5] is not None and t[4] is not None: 133 | if t[0] and t[0]!='none': 134 | _temp = t[0] 135 | if t[1] and t[1]!='none': 136 | _temp +=" | " +t[1] 137 | if t[2] and t[2]!='none': 138 | _temp += " | Size: " +self.size(t[2]) 139 | if t[3] and t[3]!='none': 140 | _temp += " | Ext: "+t[3] 141 | if t[4] and t[4]!='none': 142 | _temp += " | "+t[4] 143 | if t[5] and t[5]!='none': 144 | _temp += " | "+t[5] 145 | if t[6] and t[6]!='none': 146 | _temp += " | "+t[6] 147 | if t[7] and t[7]!='none': 148 | _temp += " | Bitrate: "+str(round(t[7],2))+" K/s" 149 | if t[8] and t[8]!='none': 150 | _temp += " | FPS: "+str(t[8]) 151 | if t[9] and t[9]!='none': 152 | _temp += " | "+t[9] 153 | 154 | self.merged_list.append(_temp) 155 | 156 | if 'subtitles' in info: 157 | try: 158 | for i in info['subtitles']: 159 | self.subs.append(str(i)+" "+str(info['subtitles'][i][0]['name'])) 160 | except: 161 | pass 162 | 163 | x.basic_formats = self.merged_list 164 | x.video_streams = self.video_list 165 | x.audio_streams = self.audio_list 166 | x.captions = self.subs 167 | x._init_tab1() 168 | x._init_tab2() 169 | x._init_download_button() 170 | x._init_download_image_button() 171 | 172 | 173 | 174 | def size(self,n): 175 | if n//1024**2>=1 and n//1024**2<1024: 176 | a=str(round((n/1024**2),2))+" MiB" 177 | return a 178 | elif n>=1024**3: 179 | a=str((round((n/1024**3),2)))+" GiB" 180 | return a 181 | elif n//1024>=1 and n//1024<1024: 182 | a=str(round(n/1024,2))+" KiB" 183 | return a -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #
ytdl ![output-onlinepngtools](https://user-images.githubusercontent.com/55890376/147201322-7cb830c8-9a47-4bbb-ad0b-d79d4c09b58a.png) 2 |

3 | GitHub release (latest by date) 4 | GitHub release (latest by date) GitHub 5 | YouTube Channel Views 6 | GitHub commit activity python python Windows 7 |

8 |

9 | python 10 |
11 | python 12 | python 13 |

14 |

15 | GitHub release (latest by date) 16 |

17 |

18 | GitHub release (latest by date) 19 |

20 | 21 | A GUI program that runs on top of yt-dlp and ffmpeg to download videos and audio. **This project is only for educational purpose DO NOT SELL . DO NOT 22 | plagiarize. USE AT YOUR RISK . I DO NOT PROMOTE ANY ILLEGAL DOWNLOADS .**
23 | 24 |                     ![python](https://user-images.githubusercontent.com/55890376/208916421-d983d873-8dd8-4d53-949a-16959f9f6df9.png)     25 | ![yt-dlp](https://user-images.githubusercontent.com/55890376/208916592-4cfc3036-6b0b-4ccf-9fa7-5214c9440bc5.png)     26 | ![ffmpeg](https://user-images.githubusercontent.com/55890376/208916616-76aace34-17e9-4865-b73c-bd1777b416bc.png)     27 | ![ps](https://user-images.githubusercontent.com/55890376/208916682-7d245f1a-1881-42dc-b56c-b3ff8fe11939.png)     28 | 29 | **Version 23.xx.yy coming soon....**
30 | Update model 3 will built using python 3.8.10
31 | Windows 7 support will be dropped in Oct 2024.
32 | **⚠️UPDATE : [ytdl](https://github.com/sourabhkv/ytdl) will focus on supporting legacy systems till Oct,2024 with little to no compromise on features. [ytdl unlocked](https://github.com/ytdl-official/unlock) will focus on enhancing look and feel, features of application with no compromise, will use higher version of Python 3.11.x and only x64 builds will be developed and maintained**.
33 | ~~⚠️ ALERT CURRENTLY SUPPORTED VERSIONS WILL NO LONGER RECEIVE UPDATES ,YTDL WILL USE **PYTHON 3.10.7** with latest libraries
34 | **⚠️ Windows 7 version will be released separately which will be based on python 3.8.10**
~~ 35 | 36 | 37 | 38 | [yt-dlp](https://github.com/yt-dlp/yt-dlp) and [youtube-dl](https://github.com/ytdl-org/youtube-dl) licensed under [The Unlicense](https://unlicense.org/)
39 | [FFmpeg](https://ffmpeg.org/) is licensed under the [GNU Lesser General Public License (LGPL)](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) version 2.1 or later.
40 | [AtomicParsley](https://github.com/wez/atomicparsley) is licensed under [GPL-2.0 license](https://github.com/wez/atomicparsley/blob/master/COPYING)
41 | [pygame](https://www.pygame.org/news) is licensed under [GNU LGPL version 2.1](https://www.gnu.org/copyleft/lesser.html) 42 | 43 | 44 | ![me at zoo](https://user-images.githubusercontent.com/55890376/203128753-56e932a5-89fc-4470-bc9c-bfb8300f6f2b.png) 45 | 46 | 47 | 48 | 49 | [More Screenshots](https://github.com/sourabhkv/ytdl/tree/main/screenshots)
50 | [Installation demo](https://www.youtube.com/watch?v=PUY7VNR4Ql8)
51 | [Full demo](https://www.youtube.com/watch?v=EZfyzXdNv9s)
52 | 53 | 54 | | Features⬇️\Application➡️ | [Ytdl](https://github.com/sourabhkv/ytdl) | [4k downlader](https://www.4kdownload.com/products/videodownloader/10) | [YTD Downloader](https://www.ytddownloader.com/) |[yt1s website](https://yt1s.com/en300)| 55 | |:---: |:---: |:---: |:---: |:---: | 56 | |Video download YouTube| yes |yes|yes|yes| 57 | |playlist download|yes|yes|no|no| 58 | |Specific video inside playlist|[yes](https://github.com/sourabhkv/ytdl/blob/main/README.md#playlist)|no|no|no| 59 | |Audio download|[yes](https://github.com/sourabhkv/ytdl/edit/main/README.md#music) |yes |no (Pro)|yes (mp3,m4a)| 60 | |Converter|yes|no|yes|no| 61 | |8K support|yes|yes|yes|no| 62 | |Ads|no|no|yes|yes| 63 | |Downlaod limit|no limit|5/day|no limit|no limit| 64 | |thumbnail download|yes|no|no|no| 65 | |Captions support|yes|yes|no|no| 66 | |Proxy support|yes|no|no|no| 67 | |Supported website|[1700+](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md)|[11](https://www.4kdownload.com/faq/faq-what-sites-are-supported)|[48](https://www.ytddownloader.com/video_sites.html)|2| 68 | |Simultaneous downloads |upto 5|Pro|Pro|No| 69 | |Create custom command|yes|no|no|no| 70 | |Metadata|yes|no|no|no| 71 | |Updates|yes|yes|yes|-| 72 | |Size of application|37 Mib|78.5 Mib|13.7 Mib|-| 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | Screenshots of changes
82 | 83 | ![ytdlgui3](https://user-images.githubusercontent.com/55890376/146916497-d6422aaa-ea57-4bdc-bf44-e336a1034aba.jpg) 84 | 85 | 86 | 87 | 88 | 89 | 90 | Version 22.208.02 and above with dark theme and categorized data.
91 | 92 | ![youtube-dl GUI new](https://user-images.githubusercontent.com/55890376/154851022-a187920a-cd3e-4b81-8d4d-b4d77b1095d6.jpg) 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | Select audio ,video and caption stream(s).
103 | Click **Browse** to browse the location where video/audio will be saved **if not clicked default browse location is downloads**
104 | 105 | ## Special features
106 | Captions support (YouTube)
107 | Thumbnail download
108 | Advanced option to specifically select audio , video separately
109 | Convert video into music MP3(64,128,320 KB/S), M4A , WAV ,FLAC WITH METADATA
110 | Supports upto 5 Youtube downloads parallely
111 | Full application can be updated by clicking on update button (including yt-dlp) version 22.0526.23 or higher required
112 | Variety of supported websites
113 | Proxy support.
114 | Rate limit
115 | File converter 116 | 117 | ## Ytdl unlocked (Pro) 118 | Expanded supported websites
119 | made with python 3.8 and 3.10
120 | multi video supports que
121 | multi video supports more URLs other than youtube.com
122 | Hyper user
123 | Wav format for playlist and more.
124 | 125 | ## Roadmap of releases
126 | :ballot_box_with_check:`Alsina` - Settings menu, history of saved files , auto update(June end)
127 | :ballot_box_with_check:`Transition release` - Help users with old version to migrate to Meteor release
128 | :ballot_box_with_check:`Meteor` - updater changes stable(July-Aug)
129 | :ballot_box_with_check:`Clang` - Regex support in output template, (Sept-Oct) 130 | 131 |

Coming features

132 | Better playlist support.
133 | tag editor.
134 | Output template with regex support. 135 | 136 | 137 | 138 | 139 | ## Stream selection
140 | If you have VLC not installed try using these combination. 141 | Recommended combination of audio and video codecs
142 | MP4 --> video: avc1 & audio: mp4a
143 | WEBM --> video: VP9 & audio : OPUS
144 | NOTE : Files are converted into MKV format since other combination are incompatible
145 | 146 | ### Music
147 | mp3 64K, mp3 320K, m4a, wav, flac available
148 | m4a, mp3 320K, flac includes thumbnail and Metadata
149 | flac , wav formats takes more space than mp3 and m4a 150 |
151 | 152 | ### Playlist
153 | Currently supports Youtube playlist
154 | Enter playlist url select format (144p,240p...,mp3,m4a) hit download
155 | 156 | Download specific episodes in playlist
157 | Playlist video items to download. Specify indices of the videos in the playlist separated by commas like: 158 | `1,2,5,8` if you want to download videos indexed 1, 2, 5, 8 in the playlist. You can specify range: `1-3,7,10-13`, it will download the videos at index 1, 2, 3, 7,10, 11, 12 and 13.
159 | If nothing is specified whole playlist will be downloaded. 160 | 161 | [New version demo](https://drive.google.com/file/d/1CWW5YTK7MjIQ3ZyQdsN7TSNu_K-F3hxN/view?usp=sharing)
162 | [old version demo](https://drive.google.com/file/d/1OaQTnjXC8wvLKSkWYx_8j8pEyUu7IYXq/view?usp=sharing)
163 | [Playlist demo](https://user-images.githubusercontent.com/55890376/168638995-183ba08b-91ac-4a72-a6c1-bf152d71c0ea.mp4)
164 | Older version [watch demo here](https://user-images.githubusercontent.com/55890376/114445050-398c9100-9bed-11eb-9b17-aea0be0704d8.mp4)
165 | 166 | ## ⚠️ DEPRECATION WARNING 167 | Windows 7 support will be dropped in Oct 2024.
168 | Playlist items selector will no longer be based on text input. 169 | Direct terminal option will be shifted to custom command section.(Discontinued)
170 | Full installer will not use program files instead will use `{localappdata}` dir, it will remove admin requirement. Program folder will not be used in future. To update uninstall existing version and install August version when available (Updater will not work for older version). 171 | 172 | ## INSTALLATION 173 | Currently this is supported only on windows.
174 | System Requirement : Windows 7 SP1 or above (x86 & x64)
175 | ### Installing via powershell 176 | Execute the following command in powershell 177 | ```powershell 178 | iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/sourabhkv/ytdl/main/WebInstaller%20CLI.ps1')) 179 | ``` 180 | 181 | ### Setup 182 | |Installer Type |Name| 183 | |---------------|:---: | 184 | |Full Installer|[Youtube-dl GUI.exe](https://github.com/sourabhkv/ytdl/releases/latest/download/YouTube-dl.GUI.exe)| 185 | |Web Installer |[Youtube-dl GUI Webinstaller.exe](https://github.com/sourabhkv/ytdl/blob/main/Youtube-dl%20Webinstaller.exe)| 186 | |Portable Installer|[Youtube-dl GUI Portable.zip](https://github.com/sourabhkv/ytdl/releases/latest/download/Youtube-dl.GUI.Portable.zip)| 187 | 188 | ⚠️ Note : Some browsers may block this download because of lack of certificate , select keep anyway 189 | : Updates will be delivered automatically. Latest 2 releases will be supported for updates. 190 | 191 | 192 | 193 | ![setup complete](https://user-images.githubusercontent.com/55890376/156933091-b3e380c3-0673-4baa-9c1d-4667d5a52f4d.png) 194 | 195 | 196 | 197 | 198 | 199 | Click Launch button after downloading.
200 | You are ready to go 🚀.
201 | 202 | ### Update application 203 | Few update model were tested in past to update application.
204 | Model 1 : Manual updates with admin rights (to make changes directly in programs folder)
205 | Model 2 : Auto updates without admin rights (using `C:\Users\\AppData\Local\Youtube-dl GUI`)
206 | Model 1 & 2 required additional component like `updater.exe` where as upcoming Model 3 no such additional component is required.
207 | Model 3 : Updater will be baked in main executable. Resulting in size reduction. ytdl will store temporary folder to store update. Each update.zip will contain script.bat to guide how changes will be made.
208 | *Model 3 is under development*
209 | 210 | ``` 211 | . 212 | .. 213 | Youtube-dl GUI 214 | |--certify 215 | *.* 216 | |--Cryptodome 217 | *.* 218 | |--database 219 | args.txt 220 | cookies.txt 221 | history.txt 222 | loc.txt 223 | log.txt 224 | output_temp_vid.txt 225 | output_temp_plst.txt 226 | |--temp 227 | script.bat 228 | Youtube-dl GUI.exe 229 | *.*(Other files depending upon update) 230 | *.* 231 | ``` 232 | 233 | If any updates are available ytdl will clear temp dir contents before downloading.
234 | After downloading update script.bat will launch to taskill & delete current Youtube-dl GUI.exe and place new Youtube-dl GUI.exe at same location to prior and perform any other task depending upon update.
235 | 236 | ## Building executable 237 | ### Windows 238 | 239 | --- 240 | 241 | 1. Clone this repository 242 | 2. Install the following dependencies 243 | ``` 244 | python >= 3.8.10 245 | ffmpeg >= 3.3.4 246 | yt-dlp latest version 247 | ``` 248 | - ffmpeg and yt-dlp should be placed in cloned dir 249 | Libraries 250 | ``` 251 | pyinstaller==5.6.2 252 | Pillow==9.4.0 253 | yt-dlp==2023.1.6 254 | virtualenv==20.17.1 255 | pycryptodomex==3.16.0 256 | requests==2.28.1 257 | psutil==5.9.4 258 | pytube==12.1.2 259 | ``` 260 | 3. Run the [builder.ps1](https://github.com/sourabhkv/ytdl/blob/main/builder.ps1) 261 | 4. Build will be generated `./dist/Youtube-dl GUI/` folder 262 | 263 | ### Linux (Still in preview) 264 | 265 | --- 266 | 267 | 1. Follow step 1 and 2 as above 268 | 2. Give permission to builder.sh 269 | ``` 270 | chmod +x builder.sh 271 | ``` 272 | - Before compiling change few lines in code which ask for logo.ico 273 | 3. Run the builder.sh 274 | ``` 275 | ./builder.sh 276 | ``` 277 | 4. Build will be generated `./dist/Youtube-dl GUI/` folder 278 | 279 | ## FAQ 280 | **Youtube-dl GUI not installing**
281 | *Click on more info then run anyway.*
282 |
283 | ![IMG-20210412-WA0007](https://user-images.githubusercontent.com/55890376/177182681-ecd68e0c-c8f8-433f-978b-70f6cbb7aa84.jpg)
284 | 285 | **URL not loading**
286 | *Sometime it takes 1-2 minute to load URL try restarting router or reconnecting internet.*
287 | 288 | **How to use multi video download?**
289 | *It only supports 5 YouTube url at max (only video playlist excluded). Paste 1st URL then copy 2nd URL then paste spaces will be automatically added. If using keyboard add spaces manually between each URL*.
290 | 291 | **How to download YouTube playlist?**
292 | *Paste Youtube URL of form `https://www.youtube.com/playlist?list=`*
293 | 294 | **How to download specific videos in playlist?**
295 | *`1,2,5,8` if you want to download videos indexed 1, 2, 5, 8 in the playlist.
296 | You can specify range: `1-3,7,10-13`, it will download the videos at index 1, 2, 3, 7,10, 11, 12 and 13.
297 | If nothing is specified whole playlist will be downloaded.*
298 | 299 | **How to download music (MP3,M4A,WAV,FLAC) ?**
300 | *After loading URL in `basic` tab click on [checkbox](https://github.com/sourabhkv/ytdl/blob/main/screenshots/ytdl2.png) which will enable music formats. Select your desired format and download.* 301 | 302 | **How to update applcation?**
303 | *Auto updater will check for updates
304 | User can click on `update` button to check for update if updates are available user shall be given choice to update. 305 | If unsupported version is detected user will be prompted to forcefully update.
306 | Latest 3 release will be supported for updates. User will be notified with message if existing version support will be dropped in future.*
307 | 308 | **Any other issues join [`t.me/ytdlgui`](https://t.me/ytdlgui)** 309 | 310 | NOTE for Windows 7 users
311 |
312 | ![image](https://user-images.githubusercontent.com/55890376/178449538-824d6501-5404-47b0-a2db-f9bd14b0c999.png)
313 |
314 | Source python.org
315 | This project is based on python 3.8.10. In future this may use python 3.9+ which is not supported in Windows 7.
316 | 317 | ## WORKING 318 | yt-dlp (youtube-dl for older version) searches streams available in website and displays streams. 319 | *sometimes there may only be only video stream(s) available or no streams at all.Using VPN might help.* 320 | User selects streams and browse location *(default location in downloads could be changed)*. 321 | ffmpeg converts it into videos/audios. 322 | if m4a is selected audio format ffmpeg uses AtomicParsley to write metadata in m4a file. 323 | Pygame window displays live download progress (for older version). 324 | 325 | ## CONTRIBUTING 326 | ### Opening an issue 327 | Bugs and suggestions should be reported at: [/ytdl/issues](https://github.com/sourabhkv/ytdl/issues) 328 | Select the type of issue 329 | - `Bug report` 330 | - `Feature request` 331 | - `Ask a question` 332 | 333 | Please see the issues section before opening issue
334 | Duplicate issues will not be entertained and make sure you are on latest version
335 | Adding Screenshots/videos will be helpful.
336 | 337 | ### Creating Pull request 338 | Do not include database files, other dependencies in PR.
339 | 340 | ## How development started and was carried?
341 | This project development started with wish to download youtube physics video(Center of mass) at 240p in March,2021 because 240p quality was managable and took decent amount of mobile data compared to 360p. 342 | Many people don't have technical knowledge of how to use youtube-dl/yt-dlp command line, to make things easier to use (started to port yt-dlp/youtube-dl CLI to GUI) ,I thought of making GUI version of youtube-dl/yt-dlp for those having no knowledge about command line program. 343 | Initially it was a very basic program which lack many features as time passed encountered many bugs and fixed it ,specially giving the live download progress in main window statusbar took almost 2 months to fix this removing black console window at startup was also challenging to fix without using `--noconsole` option. 344 | 345 | Threading helped execute many function at same time 346 | The function popens(cmd) never executed with object oriented programming (OOPs) 347 | Tkinter was easy to use and took less space compared to PySide2/PyQt5 348 | After june,2021 there was no update to youtube-dl. Development stopped for 3 months finally backend changed to yt-dlp 349 | During this development period many bugs were encountered and fixed , many features were added ,hope this project will be active in future... 350 | Need help to make this better so that anyone with poor internet connctivity can learn something. 351 | 352 | ## Support Us 353 | If you have liked my work and want to support please consider donating.
354 | Help to keep Ytdl active and running by donating. It will be really helpful and appreciated if you donate or contribute to us. Any amount is appreciated.

355 | python 356 | : `sourabhkv@upi` 357 |

358 | python 359 | :[`PinakiSahu`](https://www.paypal.com/paypalme/PinakiSahu) 360 |

361 | GitHub release (latest by date) 362 | 363 | 364 | 365 | 366 | 367 | **Want Early build access?**
368 |
GitHub release (latest by date)
369 |

370 | 371 | 372 | ⚠️ Do note these builds may be buggy and features in this builds may or may not go to stable release. 373 | 374 | ## Useful Links 375 | [Supported Websites youtube-dl](http://ytdl-org.github.io/youtube-dl/supportedsites.html)
376 | [Supported websites yt-dlp](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md)
377 | [youtube-dl](https://github.com/ytdl-org/youtube-dl)
378 | [yt-dlp](https://github.com/yt-dlp/yt-dlp)
379 | [Pytube](https://pytube.io/en/latest/)
380 | [ffmpeg](https://ffmpeg.org/ffmpeg.html)
381 | [AtomicParsley](http://atomicparsley.sourceforge.net/)
382 | [Pygame](https://www.pygame.org/wiki/about)
383 | [Inno Setup](https://jrsoftware.org/isinfo.php)
-------------------------------------------------------------------------------- /ytdl/ui/window.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import messagebox,filedialog 3 | from tkinter import * 4 | from tkinter import ttk 5 | from PIL import Image, ImageTk 6 | from tkinter.constants import DISABLED, NORMAL 7 | from tkinter import scrolledtext 8 | import tkinter.font as tkFont 9 | from .about import About 10 | from .pipe import Pipe 11 | import webbrowser 12 | import subprocess 13 | import threading 14 | import os 15 | from .settings import Settings 16 | from ..extractor.extractor import extract_info 17 | from ..downloader.matcher import Match_case 18 | 19 | class Window: 20 | def __init__(self,args): 21 | 22 | # initial setup of window 23 | self.root = tk.Tk() 24 | self.screen_width = self.root.winfo_screenwidth() 25 | self.screen_height = self.root.winfo_screenheight() 26 | _y=int((self.screen_height-640)/2) 27 | _x=int((self.screen_width-1000)/2) 28 | self.root.geometry('%dx%d+%d+%d' % (1030, 580, _x, _y)) 29 | self.root.title('YouTube-dl GUI') 30 | self.root.iconbitmap('./images/logo.ico') 31 | 32 | self.root.resizable(False,False) 33 | 34 | # style 35 | self.root.configure(bg='#303135') 36 | self.style= ttk.Style() 37 | self.style.configure('TCombobox', fieldbackground= '#525252', background= '#525252',foreground='black') 38 | self.style.configure('TButton',background='#525252') 39 | self.style.configure('TScrollbar',background='#525252',activebackground='#525252') 40 | self.style.configure('TNotebook', background='#424242',borderwidth=0) 41 | self.style.configure('TCheckbutton', background='#525252',foreground='white',activebackground='#525252',borderwidth=0) 42 | self.style.layout("TNotebook", []) 43 | 44 | 45 | # frame1 newer 46 | self.md=PhotoImage(file = './images/Frame 1newer.png') 47 | self.canvas1 = Canvas( self.root, width = 560,height = 1000) 48 | background_label = Label(image=self.md,anchor='n') 49 | background_label.place(x=0, y=-2, relwidth=1, relheight=1) 50 | self.canvas1.pack() 51 | 52 | # tab control 53 | self.tabControl = ttk.Notebook(self.root,height=236,width=624) 54 | self.tab1 = Frame(self.tabControl,background='#525252') 55 | self.tab2 = Frame(self.tabControl,background='#525252') 56 | self.tab3 = Frame(self.tabControl,background='#525252') 57 | self.tab4 = Frame(self.tabControl,background='#525252') 58 | self.tab5 = Frame(self.tabControl,background='#525252') 59 | self.tab6 = Frame(self.tabControl,background='#525252') 60 | self.tab7 = Frame(self.tabControl,background='#525252') 61 | 62 | self.tabControl.add(self.tab1, text =' Basic ') 63 | self.tabControl.add(self.tab2, text =' Advanced ') 64 | self.tabControl.add(self.tab3, text =' Playlist ') 65 | self.tabControl.add(self.tab4, text =' Multi video ') 66 | self.tabControl.add(self.tab5, text =' Converter ') 67 | self.tabControl.add(self.tab6, text =' Custom command ') 68 | self.tabControl.add(self.tab7, text =' History ') 69 | self.tabControl.place(x=35,y=215)#275 70 | 71 | 72 | Label(self.root, text = 'Youtube-dl GUI',bg='#303135',font=('Arial', 18),fg='white').place(x = 230,y = 10) 73 | _logo_image = Image.open('./images/logo.png') 74 | _resize_image = _logo_image.resize((25, 25)) 75 | _img = ImageTk.PhotoImage(_resize_image) 76 | _label1 = Label(self.root, image=_img,bg='#303135') 77 | _label1.image = _img 78 | _label1.place(x=410,y=10) 79 | 80 | _explore = Label(self.root, text = 'Explore ytdl Unlocked',fg='yellow',cursor='hand2',bg='#303135') 81 | _explore.place(x = 530,y = 20) 82 | _explore.bind('', lambda e: webbrowser.open('https://github.com/sourabhkv/ytdl/blob/main/README.md#ytdl-unlocked-pro')) 83 | 84 | 85 | # statusbar 86 | self.status = StringVar() 87 | self.status.set('') 88 | self.statusbar = tk.Label(self.root, textvariable=self.status ,width=150, bd=2,fg='white', relief=tk.SUNKEN, anchor=tk.W,bg='#202125') 89 | self.statusbar.place(x=-2,y=560) 90 | 91 | # url textfield 92 | self.url_box = Entry(self.root,bg='#A1A1A1',width=80,bd=0,fg='black') 93 | if len(args)>=2: 94 | temp_args = args[1:] 95 | self.url_box.insert(END," ".join(temp_args)) 96 | self.on_go() 97 | self.url_box.bind('', lambda e:self.on_go()) 98 | self.url_box.place(x=60,y=103) 99 | 100 | # URL text 101 | self._URL_text_var = StringVar() 102 | self._URL_text_var.set("URL") 103 | self._url_label = Label(self.root, textvariable = self._URL_text_var ,bg="#424242",fg="white") 104 | self._url_label.place(x = 55,y = 76) 105 | 106 | # Output text 107 | self._output_text_var = StringVar() 108 | self._output_text_var.set("Output") 109 | self._output_label = Label(self.root, textvariable = self._output_text_var,bg="#424242",fg="white",cursor='hand2') 110 | self._output_label.bind('',lambda a: self._output_label.config(fg="#0574FF")) 111 | self._output_label.bind('',lambda a: self._output_label.config(fg="white")) 112 | self._output_label.bind("", lambda e: self.open_dir()) 113 | self._output_label.place(x = 55,y = 148) 114 | 115 | # location field 116 | self.location_box = Entry(self.root,bg='#A1A1A1',width=80,bd=0,fg='black') 117 | with open('./config/loc.txt') as file: 118 | self.download_Directory = file.readlines()[0] 119 | self.location_box.insert(0,self.download_Directory) 120 | self.location_box.place(x=60,y=179) 121 | 122 | # go button 123 | self.go_button_image = PhotoImage(file = './images/go.png') 124 | self.go_button_red_image = PhotoImage(file = './images/gored.png') 125 | self.go_button = Button(self.root,text='GO',bd=0,image=self.go_button_image,command=lambda: self.on_go(),bg='#424242',activebackground='#424242',highlightthickness = 0) 126 | self.go_button.bind('',lambda a:self.color_changer(self.go_button, self.go_button_red_image)) 127 | self.go_button.bind('',lambda a:self.color_changer(self.go_button, self.go_button_image)) 128 | self.go_button.place(x=570,y=96) 129 | 130 | # paste button 131 | self.paste_button_image = PhotoImage(file = './images/paste.png') 132 | self.paste_button_red_image = PhotoImage(file = './images/pastered.png') 133 | self.paste_button = Button(self.root,text='Paste',bd=0,image=self.paste_button_image,command=lambda: self.paste_on_text(),bg='#424242',activebackground='#424242',highlightthickness = 0) 134 | self.paste_button.bind('',lambda a:self.color_changer(self.paste_button, self.paste_button_red_image)) 135 | self.paste_button.bind('',lambda a:self.color_changer(self.paste_button, self.paste_button_image)) 136 | self.paste_button.place(x=200,y=134) 137 | 138 | # clear button 139 | self.clear_button_image = PhotoImage(file = './images/clear.png') 140 | self.clear_button_red_image = PhotoImage(file = './images/clearred.png') 141 | self.clear_button = Button(self.root,text='clear',bd=0,image=self.clear_button_image,command=lambda: self.url_box.delete(0,END),bg='#424242',activebackground='#424242',highlightthickness = 0) 142 | self.clear_button.bind('',lambda a:self.color_changer(self.clear_button, self.clear_button_red_image)) 143 | self.clear_button.bind('',lambda a:self.color_changer(self.clear_button, self.clear_button_image)) 144 | self.clear_button.place(x=300,y=134) 145 | 146 | # browse button 147 | self.browse_button_image = PhotoImage(file = './images/browse.png') 148 | self.browse_button_red_image = PhotoImage(file = './images/browred.png') 149 | self.browse_button = Button(self.root,text='browse',bd=0,image=self.browse_button_image,command=lambda: self.browse_location(),bg='#424242',activebackground='#424242',highlightthickness = 0) 150 | self.browse_button.bind('',lambda a:self.color_changer(self.browse_button, self.browse_button_red_image)) 151 | self.browse_button.bind('',lambda a:self.color_changer(self.browse_button, self.browse_button_image)) 152 | self.browse_button.place(x=570,y=174) 153 | 154 | # About button 155 | self.about_button_image = PhotoImage(file = './images/img3.png') 156 | self.about_button_red_image = PhotoImage(file = './images/aboutdark.png') 157 | self.about_button = Button(self.root,text='about',bd=0,image=self.about_button_image,command=lambda: self.about_project(),bg='#424242',activebackground='#424242',highlightthickness = 0) 158 | self.about_button.bind('',lambda a:self.color_changer(self.about_button, self.about_button_red_image)) 159 | self.about_button.bind('',lambda a:self.color_changer(self.about_button, self.about_button_image)) 160 | self.about_button.place(x=28,y=510) 161 | 162 | # update button 163 | self.update_button_image = PhotoImage(file = './images/img4.png') 164 | self.update_button_red_image = PhotoImage(file = './images/updatedark.png') 165 | self.update_button = Button(self.root,text='update',bd=0,image=self.update_button_image,command=lambda: self.update_project(),bg='#424242',activebackground='#424242',highlightthickness = 0) 166 | self.update_button.bind('',lambda a:self.color_changer(self.update_button, self.update_button_red_image)) 167 | self.update_button.bind('',lambda a:self.color_changer(self.update_button, self.update_button_image)) 168 | self.update_button.place(x=120,y=510) 169 | 170 | # donate button 171 | self.donate_button_image = PhotoImage(file = './images/donatelight.png') 172 | self.donate_button_red_image = PhotoImage(file = './images/donatedark.png') 173 | self.donate_button = Button(self.root,text='donate',bd=0,image=self.donate_button_image,command=lambda: webbrowser.open("https://github.com/sourabhkv/ytdl#support-us"),bg='#424242',activebackground='#424242',highlightthickness = 0) 174 | self.donate_button.bind('',lambda a:self.color_changer(self.donate_button, self.donate_button_red_image)) 175 | self.donate_button.bind('',lambda a:self.color_changer(self.donate_button, self.donate_button_image)) 176 | self.donate_button.place(x=216,y=510) 177 | 178 | # GitHub button 179 | self.Github_button_image = PhotoImage(file = './images/img6.png') 180 | self.Github_button_red_image = PhotoImage(file = './images/Githubdark.png') 181 | self.Github_button = Button(self.root,text='Github',bd=0,image=self.Github_button_image,command=lambda: webbrowser.open("https://github.com/sourabhkv/ytdl"),bg='#424242',activebackground='#424242',highlightthickness = 0) 182 | self.Github_button.bind('',lambda a:self.color_changer(self.Github_button, self.Github_button_red_image)) 183 | self.Github_button.bind('',lambda a:self.color_changer(self.Github_button, self.Github_button_image)) 184 | self.Github_button.place(x=324,y=510) 185 | 186 | # Supported websites button 187 | self.supported_web_button_image = PhotoImage(file = './images/img7.png') 188 | self.supported_web_button_red_image = PhotoImage(file = './images/supportedwebsitesdark.png') 189 | self.supported_web_button = Button(self.root,text='supported_web',bd=0,image=self.supported_web_button_image,command=lambda: webbrowser.open("https://supported_web.com/yt-dlp/yt-dlp/blob/master/supportedsites.md"),bg='#424242',activebackground='#424242',highlightthickness = 0) 190 | self.supported_web_button.bind('',lambda a:self.color_changer(self.supported_web_button, self.supported_web_button_red_image)) 191 | self.supported_web_button.bind('',lambda a:self.color_changer(self.supported_web_button, self.supported_web_button_image)) 192 | self.supported_web_button.place(x=420,y=510) 193 | 194 | # settings button 195 | self.settings_button_image = PhotoImage(file = './images/settings.png') 196 | self.settings_button_red_image = PhotoImage(file = './images/settingsdark.png') 197 | self.settings_button = Button(self.root,text='settings',bd=0,image=self.settings_button_image,command=lambda: self.settings_class.Settings_page(self.screen_height,self.screen_width),bg='#424242',activebackground='#424242',highlightthickness = 0) 198 | self.settings_button.bind('',lambda a:self.color_changer(self.settings_button, self.settings_button_red_image)) 199 | self.settings_button.bind('',lambda a:self.color_changer(self.settings_button, self.settings_button_image)) 200 | self.settings_button.place(x=585,y=510) 201 | 202 | # description box 203 | self.frame1=Frame(self.root,bg = "#303135",width=280,height=200) 204 | self.frame1.place(x=708,y=275) 205 | self.sb_ver= ttk.Scrollbar(self.frame1) 206 | self.description_box = Text(self.frame1,height=13,width=41,highlightthickness=0,relief=FLAT,yscrollcommand=self.sb_ver.set) 207 | self.description_box.config(font= tkFont.Font(family="Arial", size=10),state= NORMAL,background="#404040",fg="grey")#404040 208 | self.sb_ver.config(command=self.description_box.yview) 209 | self.sb_ver.pack(side=RIGHT, fill=Y) 210 | self.description_box.config(state=DISABLED) 211 | self.description_box.pack(side=LEFT) 212 | 213 | # Custom command page 214 | Label(self.tab6, text="yt-dlp ARGS",fg="white",bg="#525252").place(x=8,y=10) 215 | Label(self.tab6, text = "eg. -F ",fg="white",bg="#525252").place(x = 535,y = 210) 216 | 217 | self.custom_cmd=Entry(self.tab6,bg='#303135',width=55,bd=0,fg='white',font=('Microsoft Sans Serif',10)) 218 | self.custom_cmd.place(x=82,y=10) 219 | 220 | self.text_area = scrolledtext.ScrolledText(self.tab6, wrap=tk.WORD,width=83, height=11,font=('Microsoft Sans Serif',9),background="#404040",fg="white",highlightthickness=0,relief=FLAT) 221 | self.text_area.grid(column=0, row=2, pady=40, padx=10) 222 | 223 | _terminal_label = Label(self.tab6, text = "Terminal",fg="cyan",cursor="hand2",bg="#525252") 224 | _terminal_label.bind("", lambda e : subprocess.Popen('start terminal.bat',shell=True) ) 225 | _terminal_label.place(x = 290,y = 210) 226 | 227 | _help_label = Label(self.tab6, text = "How to use ?",fg="orange",cursor="hand2",bg="#525252") 228 | _help_label.bind("", lambda e: webbrowser.open('https://github.com/yt-dlp/yt-dlp#usage-and-options')) 229 | _help_label.place(x = 10,y = 210) 230 | 231 | _run_button = ttk.Button(self.tab6, text = "RUN",command=lambda : self.custom()) 232 | _run_button.config(width=8) 233 | _run_button.place(x=553,y=10) 234 | 235 | self.imgpst = PhotoImage(file = "./images/paste2.png") 236 | self.paste_custom = Button(self.tab6, text = "Paste",fg="blue",bd=0,bg="#525252",image=self.imgpst,command=lambda : self.paste2_on_text() ,activebackground='#525252',highlightthickness = 0) 237 | self.paste_custom.place(x=480,y=10) 238 | 239 | self.imgclr = PhotoImage(file = "./images/clr2.png") 240 | self.clear_custom = Button(self.tab6, text = "Clear",fg="blue",bd=0,bg="#525252",image=self.imgclr,command=lambda : self.clear_custom_cmd_box() ,activebackground='#525252',highlightthickness = 0) 241 | self.clear_custom.place(x=515,y=10) 242 | 243 | # other class declaration 244 | self.custom_class = Pipe() 245 | self.settings_class = Settings() 246 | self.match_downloader = Match_case() 247 | 248 | # list declaration 249 | self.basic_formats = [] 250 | self.audio_formats = ["Mp3 64 kbps","Mp3 128 kbps","Mp3 320 kbps","M4A high","Wav Lossless","Flac Lossless"] 251 | self.video_streams = [] 252 | self.audio_streams = [] 253 | self.captions = [] 254 | self.playlist_format =[] 255 | 256 | 257 | # tab elements area 258 | #self._init_tab1() 259 | #self._init_tab2() 260 | #self._init_tab3() 261 | 262 | # tab1 263 | def _init_tab1(self): 264 | self.video_quality_var = StringVar() 265 | self.video_quality_var.set("Select video Quality") 266 | Label(self.tab1, textvariable = self.video_quality_var,bg="#525252",fg="white").place(x = 20,y = 20) 267 | 268 | self.basic_combobox = ttk.Combobox(self.tab1, width="90", values=self.basic_formats,state="readonly") 269 | self.basic_combobox.place(x=20,y=47) 270 | 271 | self.ischecked_music = BooleanVar() 272 | self.ischecked_music.set(False) 273 | self.check_music=ttk.Checkbutton(self.tab1,text = "Convert to music", var=self.ischecked_music, onvalue=True, offvalue=False, command=self.isChecked) 274 | self.check_music.place(x=3,y=85) 275 | 276 | self.music_combobox = ttk.Combobox(self.tab1, width="45", values=self.audio_formats,state="readonly") 277 | self.music_combobox.place(x=20,y=110) 278 | self.music_combobox.config(state=DISABLED) 279 | 280 | self.clrtab1 = Label(self.tab1, text = "Clear selection",fg="#0090FF",cursor="hand2",bg="#525252") 281 | self.clrtab1.bind("", lambda g: self.clearselection1()) 282 | self.clrtab1.place(x = 515,y = 200) 283 | 284 | #tab2 285 | def _init_tab2(self): 286 | self.video_streams_var = StringVar() 287 | self.video_streams_var.set("Video Streams") 288 | Label(self.tab2, textvariable = self.video_streams_var ,bg="#525252",fg="white").place(x = 20,y = 10) 289 | 290 | self.video_streams_combobox = ttk.Combobox(self.tab2, width="95", values=self.video_streams,state="readonly") 291 | self.video_streams_combobox.place(x=15,y=32) 292 | 293 | self.audio_streams_var = StringVar() 294 | self.audio_streams_var.set("Audio Streams") 295 | Label(self.tab2, textvariable = self.audio_streams_var ,bg="#525252",fg="white").place(x = 20,y = 55) 296 | 297 | self.audio_streams_combobox = ttk.Combobox(self.tab2, width="95", values=self.audio_streams,state="readonly") 298 | self.audio_streams_combobox.place(x=15,y=77) 299 | 300 | self.captions_var = StringVar() 301 | self.captions_var.set("Subtitles") 302 | Label(self.tab2, textvariable = self.captions_var ,bg="#525252",fg="white").place(x = 20,y = 100) 303 | 304 | self.captions_combobox = ttk.Combobox(self.tab2, width="37", values=self.captions ,state="readonly") 305 | self.captions_combobox.place(x=15,y=122) 306 | 307 | self.format_var = StringVar() 308 | self.format_var.set("Format") 309 | Label(self.tab2, textvariable = self.format_var,bg="#525252",fg="white").place(x = 280,y = 100) 310 | 311 | self.format_checkbox=ttk.Combobox(self.tab2, width="20", values=["auto-detect","mp4","mkv","webm"],state="readonly") 312 | self.format_checkbox.set("auto-detect") 313 | self.format_checkbox.place(x=327-50,y=122) 314 | 315 | self.ratelim_var = StringVar() 316 | self.ratelim_var.set("Rate limit(eg. 50K or 4.2M)") 317 | Label(self.tab2, textvariable = self.ratelim_var ,bg="#525252",fg="white").place(x = 442,y = 100) 318 | 319 | self.ratelim_entry=ttk.Entry(self.tab2,width=27) 320 | self.ratelim_entry.place(x=437,y=122) 321 | 322 | self.custom_filename_var = StringVar() 323 | self.custom_filename_var.set("Custom Filename") 324 | Label(self.tab2, textvariable = self.custom_filename_var,bg="#525252",fg="white").place(x = 20,y = 146) 325 | 326 | self.customfile_entry = ttk.Entry(self.tab2,width=46) 327 | self.customfile_entry.place(x=17,y=169) 328 | 329 | self.proxy_var = StringVar() 330 | self.proxy_var.set("Proxy URL") 331 | Label(self.tab2, textvariable = self.proxy_var,bg="#525252",fg="white").place(x = 330,y = 146) 332 | 333 | self.proxy_entry = ttk.Entry(self.tab2,width=46) 334 | self.proxy_entry.place(x=323,y=169) 335 | 336 | self.embdth_var = BooleanVar() 337 | self.embdth_var.set(False) 338 | self.embdth=ttk.Checkbutton(self.tab2,text="Embed thumbnail", var=self.embdth_var, onvalue=True, offvalue=False) 339 | self.embdth.place(x=17,y=199) 340 | 341 | self.clrtab2 = Label(self.tab2, text = "Clear selection",fg="#0090FF",cursor="hand2",bg="#525252") 342 | self.clrtab2.bind("", lambda g: self.clearselection2()) 343 | self.clrtab2.place(x = 515,y = 200) 344 | 345 | #tab3 346 | def _init_tab3(self): 347 | self.playlist_format_var = StringVar() 348 | self.playlist_format_var.set("Select format") 349 | Label(self.tab3, textvariable = self.playlist_format_var,bg="#525252",fg="white").place(x = 170,y = 20) 350 | 351 | self.playlist_format = ttk.Combobox(self.tab3, width="45", values=self.playlist_format,state="readonly") 352 | self.playlist_format.place(x=170,y=45) 353 | 354 | self.playlist_items_var = StringVar() 355 | self.playlist_items_var.set("Select items") 356 | Label(self.tab3, textvariable = self.playlist_items_var,bg="#525252",fg="white").place(x = 170,y = 70) 357 | 358 | self.playlist_items = ttk.Entry(self.tab3,width=47) 359 | self.playlist_items.place(x=170,y=95) 360 | 361 | self.clrtab3 = Label(self.tab3, text = "Clear selection",fg="#0090FF",cursor="hand2",bg="#525252") 362 | self.clrtab3.bind("", lambda g: self.clearselection3()) 363 | self.clrtab3.place(x = 515,y = 200) 364 | 365 | # download thumbnail button 366 | def _init_download_button(self): 367 | self.download_thumbnail_button_image = PhotoImage(file = './images/img13.png') 368 | self.download_thumbnail_button_red_image = PhotoImage(file = './images/thred.png') 369 | self.download_thumbnail_button = Button(self.root,text='download thumbnail',bd=0,image=self.download_thumbnail_button_image,command=lambda: self.settings_class.Settings_page(self.screen_height,self.screen_width),bg='#424242',activebackground='#424242',highlightthickness = 0) 370 | self.download_thumbnail_button.bind('',lambda a:self.color_changer(self.download_thumbnail_button, self.download_thumbnail_button_red_image)) 371 | self.download_thumbnail_button.bind('',lambda a:self.color_changer(self.download_thumbnail_button, self.download_thumbnail_button_image)) 372 | self.download_thumbnail_button.place(x=765,y=229) 373 | 374 | # download button 375 | def _init_download_image_button(self): 376 | self.download_button_image = PhotoImage(file = './images/img9.png') 377 | self.download_button_red_image = PhotoImage(file = './images/dwred.png') 378 | self.download_button = Button(self.root,text='download thumbnail',bd=0,image=self.download_button_image,command=lambda: self.match_downloader.checking(self),bg='#424242',activebackground='#424242',highlightthickness = 0) 379 | self.download_button.bind('',lambda a:self.color_changer(self.download_button, self.download_button_red_image)) 380 | self.download_button.bind('',lambda a:self.color_changer(self.download_button, self.download_button_image)) 381 | self.download_button.place(x=805,y=506) 382 | 383 | def clearselection1(self): 384 | self.basic_combobox.set("") 385 | self.music_combobox.set("") 386 | self.ischecked_music.set(False) 387 | 388 | def clearselection2(self): 389 | self.video_streams_combobox.set("") 390 | self.audio_streams_combobox.set("") 391 | self.captions_combobox.set("") 392 | 393 | def clearselection2(self): 394 | self.playlist_format.set("") 395 | self.playlist_items.delete(0,END) 396 | 397 | def on_go(self): 398 | _info = extract_info(self) 399 | tv2 = threading.Thread(target=_info.search,args=(self,)) 400 | tv2.start() 401 | self.status.set(' [Loading...]') 402 | 403 | def color_changer(self,b,a): 404 | b.config(image=a) 405 | 406 | def on_change(self): 407 | _text = self.url_box.get() 408 | if not len(_text): 409 | messagebox.showerror('Youtube-dl GUI','Enter URL') 410 | 411 | def isChecked(self): 412 | if self.ischecked_music.get(): 413 | self.music_combobox.config(state=NORMAL) 414 | self.music_combobox.config(state='readonly') 415 | self.music_combobox.focus_force() 416 | else: 417 | self.music_combobox.config(state='readonly') 418 | self.music_combobox.config(state=DISABLED) 419 | 420 | def paste_on_text(self): 421 | try: 422 | _AnnoyingWindow=Tk() 423 | _ClipBoard = _AnnoyingWindow.clipboard_get() 424 | _AnnoyingWindow.destroy() 425 | _ele=_ClipBoard 426 | self.url_box.insert(END, _ele ) 427 | 428 | except: 429 | _AnnoyingWindow.destroy() 430 | messagebox.showerror('Youtube-dl GUI','URL should be in text') 431 | 432 | def paste2_on_text(self): 433 | try: 434 | _AnnoyingWindow=Tk() 435 | _ClipBoard = _AnnoyingWindow.clipboard_get() 436 | _AnnoyingWindow.destroy() 437 | _ele=_ClipBoard 438 | self.custom_cmd.insert(END, ' '+_ele ) 439 | print(self.custom_cmd.get()) 440 | 441 | except: 442 | _AnnoyingWindow.destroy() 443 | messagebox.showerror('Youtube-dl GUI','URL should be in text') 444 | 445 | def clear_custom_cmd_box(self): 446 | self.custom_cmd.delete(0,END) 447 | self.text_area.delete(0.0,END) 448 | 449 | def browse_location(self): 450 | self.download_Directory = filedialog.askdirectory(initialdir='YOUR DIRECTORY PATH') 451 | if self.download_Directory=='': 452 | with open('./config/loc.txt','r') as file: 453 | self.download_Directory=file.readlines()[0] 454 | 455 | self.location_box.delete(0,END) 456 | self.location_box.insert(0,self.download_Directory) 457 | with open('./config/loc.txt','w+') as file: 458 | file.write(self.download_Directory) 459 | 460 | def description_modifier(self,_data): 461 | self.description_box.config(state=NORMAL) 462 | self.description_box.delete(1.0,"end") 463 | self.description_box.insert(1.0, _data) 464 | self.description_box.configure(state=DISABLED) 465 | 466 | def about_project(self): 467 | About(self.screen_height, self.screen_width) 468 | 469 | def open_dir(self): 470 | if os.path.exists(self.location_box.get()): 471 | subprocess.Popen(r'explorer /open, '+self.location_box.get().replace("/","\\")) 472 | else: 473 | messagebox.showerror("Youtube-dl GUI","Path doesn't exist\nPath might be deleted or moved") 474 | 475 | def custom(self): 476 | if len(self.custom_cmd.get()) > 0: 477 | _cmd="yt-dlp "+self.custom_cmd.get() 478 | t2 = threading.Thread(target=self.custom_class.run_commandcustom, args=(self.root,self.text_area,_cmd,)) 479 | t2.start() 480 | else: 481 | messagebox.showerror("Youtube-dl GUI","Enter command") --------------------------------------------------------------------------------