├── .gitignore ├── .idea ├── .gitignore ├── git_toolbox_prj.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── markdown.xml ├── misc.xml ├── modules.xml ├── openai-python-playground.iml └── vcs.xml ├── README.md ├── Screenshot.png ├── download.ico ├── download.png ├── favicon.ico ├── main.py ├── makefile └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | ### JetBrains template 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 3 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 4 | 5 | # User-specific stuff 6 | .idea/**/workspace.xml 7 | .idea/**/tasks.xml 8 | .idea/**/usage.statistics.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # Generated files 13 | .idea/**/contentModel.xml 14 | 15 | # Sensitive or high-churn files 16 | .idea/**/dataSources/ 17 | .idea/**/dataSources.ids 18 | .idea/**/dataSources.local.xml 19 | .idea/**/sqlDataSources.xml 20 | .idea/**/dynamic.xml 21 | .idea/**/uiDesigner.xml 22 | .idea/**/dbnavigator.xml 23 | 24 | # Gradle 25 | .idea/**/gradle.xml 26 | .idea/**/libraries 27 | 28 | # Gradle and Maven with auto-import 29 | # When using Gradle or Maven with auto-import, you should exclude module files, 30 | # since they will be recreated, and may cause churn. Uncomment if using 31 | # auto-import. 32 | # .idea/artifacts 33 | # .idea/compiler.xml 34 | # .idea/jarRepositories.xml 35 | # .idea/modules.xml 36 | # .idea/*.iml 37 | # .idea/modules 38 | # *.iml 39 | # *.ipr 40 | 41 | # CMake 42 | cmake-build-*/ 43 | 44 | # Mongo Explorer plugin 45 | .idea/**/mongoSettings.xml 46 | 47 | # File-based project format 48 | *.iws 49 | 50 | # IntelliJ 51 | out/ 52 | 53 | # mpeltonen/sbt-idea plugin 54 | .idea_modules/ 55 | 56 | # JIRA plugin 57 | atlassian-ide-plugin.xml 58 | 59 | # Cursive Clojure plugin 60 | .idea/replstate.xml 61 | 62 | # Crashlytics plugin (for Android Studio and IntelliJ) 63 | com_crashlytics_export_strings.xml 64 | crashlytics.properties 65 | crashlytics-build.properties 66 | fabric.properties 67 | 68 | # Editor-based Rest Client 69 | .idea/httpRequests 70 | 71 | # Android studio 3.1+ serialized cache file 72 | .idea/caches/build_file_checksums.ser 73 | 74 | ### Python template 75 | # Byte-compiled / optimized / DLL files 76 | __pycache__/ 77 | *.py[cod] 78 | *$py.class 79 | 80 | # C extensions 81 | *.so 82 | 83 | # Distribution / packaging 84 | .Python 85 | build/ 86 | develop-eggs/ 87 | dist/ 88 | downloads/ 89 | eggs/ 90 | .eggs/ 91 | lib/ 92 | lib64/ 93 | parts/ 94 | sdist/ 95 | var/ 96 | wheels/ 97 | share/python-wheels/ 98 | *.egg-info/ 99 | .installed.cfg 100 | *.egg 101 | MANIFEST 102 | 103 | # PyInstaller 104 | # Usually these files are written by a python script from a template 105 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 106 | *.manifest 107 | *.spec 108 | 109 | # Installer logs 110 | pip-log.txt 111 | pip-delete-this-directory.txt 112 | 113 | # Unit test / coverage reports 114 | htmlcov/ 115 | .tox/ 116 | .nox/ 117 | .coverage 118 | .coverage.* 119 | .cache 120 | nosetests.xml 121 | coverage.xml 122 | *.cover 123 | *.py,cover 124 | .hypothesis/ 125 | .pytest_cache/ 126 | cover/ 127 | 128 | # Translations 129 | *.mo 130 | *.pot 131 | 132 | # Django stuff: 133 | *.log 134 | local_settings.py 135 | db.sqlite3 136 | db.sqlite3-journal 137 | 138 | # Flask stuff: 139 | instance/ 140 | .webassets-cache 141 | 142 | # Scrapy stuff: 143 | .scrapy 144 | 145 | # Sphinx documentation 146 | docs/_build/ 147 | 148 | # PyBuilder 149 | .pybuilder/ 150 | target/ 151 | 152 | # Jupyter Notebook 153 | .ipynb_checkpoints 154 | 155 | # IPython 156 | profile_default/ 157 | ipython_config.py 158 | 159 | # pyenv 160 | # For a library or package, you might want to ignore these files since the code is 161 | # intended to run in multiple environments; otherwise, check them in: 162 | # .python-version 163 | 164 | # pipenv 165 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 166 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 167 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 168 | # install all needed dependencies. 169 | #Pipfile.lock 170 | 171 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 172 | __pypackages__/ 173 | 174 | # Celery stuff 175 | celerybeat-schedule 176 | celerybeat.pid 177 | 178 | # SageMath parsed files 179 | *.sage.py 180 | 181 | # Environments 182 | .env 183 | .venv 184 | env/ 185 | venv/ 186 | ENV/ 187 | env.bak/ 188 | venv.bak/ 189 | 190 | # Spyder project settings 191 | .spyderproject 192 | .spyproject 193 | 194 | # Rope project settings 195 | .ropeproject 196 | 197 | # mkdocs documentation 198 | /site 199 | 200 | # mypy 201 | .mypy_cache/ 202 | .dmypy.json 203 | dmypy.json 204 | 205 | # Pyre type checker 206 | .pyre/ 207 | 208 | # pytype static type analyzer 209 | .pytype/ 210 | 211 | # Cython debug symbols 212 | cython_debug/ 213 | 214 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/git_toolbox_prj.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 171 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/markdown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/openai-python-playground.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ai Downloader 2 | 3 | A python file downloader with a QT GUI. 4 | 5 | This app was almost entirely written by OpenAI Codex. I made some very small changes to finish it out(such as adding a icon). 6 | 7 | It was intended as an experiment to see if I could make a fully functioning application written in python using AI, which worked wonders as it was already running out of the prompt result. 8 | 9 | You're welcome to try it out by downloading the executable release. Don't worry it is as safe as it can be. It seems that the AI is not interested in collecting any kind of telemetry... 😅 10 | 11 | ## Screenshot 12 | 13 | ![Screenshot](Screenshot.png) 14 | 15 | ## App icon 16 | 17 | The app icon was also made using AI. Specifically Stable Diffusion. 18 | 19 | ![App icon](download.png) 20 | -------------------------------------------------------------------------------- /Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falleng0d/ai-downloader/18dde6c9f9f5fddfe6d09373f152db6b2035fd79/Screenshot.png -------------------------------------------------------------------------------- /download.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falleng0d/ai-downloader/18dde6c9f9f5fddfe6d09373f152db6b2035fd79/download.ico -------------------------------------------------------------------------------- /download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falleng0d/ai-downloader/18dde6c9f9f5fddfe6d09373f152db6b2035fd79/download.png -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falleng0d/ai-downloader/18dde6c9f9f5fddfe6d09373f152db6b2035fd79/favicon.ico -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # Python 3 2 | # Create a QT program that displays a "File Url" text field and a "Download" button. 3 | # There should be a "paste" button to the left of the file url. It will paste the contents of the clipboard on the text field 4 | # Below the "Download" button, add a progress bar that will show the download progress for the file 5 | # Right next to "Download" button, add a "Cancel" button. When the user clicks the "Cancel" button, the download should be cancelled 6 | 7 | 8 | import os 9 | import re 10 | import sys 11 | import time 12 | import urllib.request 13 | 14 | from PyQt5.QtCore import QThread, Qt, pyqtSignal 15 | from PyQt5.QtGui import QIcon 16 | from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QLineEdit, QMessageBox, QProgressBar, \ 17 | QPushButton, \ 18 | QVBoxLayout, QWidget 19 | 20 | class DownloadThread(QThread): 21 | onprogress = pyqtSignal(int) 22 | onread = pyqtSignal(int) 23 | 24 | def __init__(self, url): 25 | super().__init__() 26 | self.url = url 27 | self.filename = url.split('/')[-1] 28 | self.total_size = 0 29 | 30 | def run(self): 31 | urllib.request.urlretrieve(self.url, self.filename, self.report) 32 | 33 | def report(self, blocknum, blocksize, totalsize): 34 | readsofar = blocknum * blocksize 35 | self.total_size = totalsize 36 | if totalsize > 0: 37 | percent = min(readsofar * 100 / totalsize, 100) 38 | self.onprogress.emit(int(percent)) 39 | self.onread.emit(readsofar) 40 | # print a ascii progress bar 41 | print('\r', end='') 42 | print('#' * int(percent / 2), end='') 43 | print(' ' * (50 - int(percent / 2)), end='') 44 | print('[%d%%]' % percent, end='') 45 | sys.stdout.flush() 46 | 47 | def terminate(self): 48 | sys.stdout.write('\n') 49 | print('Download cancelled') 50 | super().terminate() 51 | 52 | class Downloader(QWidget): 53 | def __init__(self): 54 | super().__init__() 55 | self.download_thread = None 56 | self.download_started_time = None 57 | self.initUI() 58 | 59 | def initUI(self): 60 | self.setWindowTitle('Downloader') 61 | self.setWindowIcon(QIcon('download.png')) 62 | self.setGeometry(300, 300, 800, 100) 63 | 64 | self.url_label = QLabel('File Url') 65 | self.url_text = QLineEdit() 66 | self.url_text.setPlaceholderText('Enter file url') 67 | self.url_text.textChanged.connect(self.validate_url) 68 | 69 | self.paste_button = QPushButton('Paste') 70 | self.paste_button.clicked.connect(self.paste_url) 71 | 72 | self.download_button = QPushButton('Download') 73 | self.download_button.clicked.connect(self.download_file) 74 | self.download_button.setEnabled(False) 75 | 76 | self.cancel_button = QPushButton('Cancel') 77 | self.cancel_button.clicked.connect(self.cancel_download) 78 | self.cancel_button.setEnabled(False) 79 | 80 | self.progress_bar = QProgressBar() 81 | self.progress_bar.setValue(0) 82 | 83 | self.progress_text = QLabel('Progress') 84 | self.progress_text_value = QLabel('') 85 | 86 | self.progress_text_layout = QHBoxLayout() 87 | self.progress_text_layout.addWidget(self.progress_text, 1, Qt.AlignLeft) 88 | self.progress_text_layout.addWidget(self.progress_text_value) 89 | 90 | self.url_layout = QHBoxLayout() 91 | self.url_layout.addWidget(self.url_label) 92 | self.url_layout.addWidget(self.url_text) 93 | self.url_layout.addWidget(self.paste_button) 94 | 95 | self.download_layout = QHBoxLayout() 96 | self.download_layout.addWidget(self.download_button) 97 | self.download_layout.addWidget(self.cancel_button) 98 | 99 | self.main_layout = QVBoxLayout() 100 | self.main_layout.addLayout(self.url_layout) 101 | self.main_layout.addLayout(self.download_layout) 102 | self.main_layout.addLayout(self.progress_text_layout) 103 | self.main_layout.addWidget(self.progress_bar) 104 | 105 | self.setLayout(self.main_layout) 106 | 107 | def paste_url(self): 108 | clipboard = QApplication.clipboard() 109 | self.url_text.setText(clipboard.text()) 110 | 111 | def download_file(self): 112 | if self.download_thread is not None: 113 | # display a message box to the user that the download is already in progress 114 | QMessageBox.about(self, 'Download in progress', 'The download is already in progress') 115 | return 116 | 117 | self.download_thread = DownloadThread(self.url_text.text()) 118 | self.download_thread.onprogress.connect(self.progress_bar.setValue) 119 | self.download_thread.onread.connect(self.update_progress_text) 120 | self.download_thread.start() 121 | self.download_button.setEnabled(False) 122 | self.download_started_time = time.time() 123 | 124 | self.cancel_button.setEnabled(True) 125 | 126 | def update_progress_text(self, readsofar): 127 | if self.download_thread is None: 128 | return 129 | total_size = self.download_thread.total_size 130 | total_size_mb = total_size / 1024 / 1024 131 | readsofar_mb = readsofar / 1024 / 1024 132 | speed = readsofar / (time.time() - self.download_started_time) 133 | self.progress_text_value.setText(f'{readsofar_mb:.2f}/{total_size_mb:.2f} mb at {speed / 1024 / 1024:.2f} mb/s') 134 | 135 | def validate_url(self): 136 | # validates the provided url using regex and disables the download button if the url is invalid 137 | url_regex = re.compile( 138 | r'^(?:http|ftp)s?://' # http:// or https:// 139 | r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... 140 | r'localhost|' # localhost... 141 | r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip 142 | r'(?::\d+)?' # optional port 143 | r'(?:/?|[/?]\S+)$', re.IGNORECASE) 144 | if url_regex.match(self.url_text.text()): 145 | self.download_button.setEnabled(True) 146 | else: 147 | self.download_button.setEnabled(False) 148 | 149 | def cancel_download(self): 150 | filename = self.download_thread.filename 151 | self.download_thread.terminate() 152 | self.progress_bar.setValue(0) 153 | # remove the file if it was partially downloaded 154 | if os.path.exists(filename): 155 | try: 156 | os.remove(filename) 157 | except PermissionError: 158 | # schedule the file for deletion in 5 seconds 159 | os.system(f'del /f /q /s /t 5 {filename}') 160 | self.download_thread = None 161 | self.cancel_button.setEnabled(False) 162 | self.download_button.setEnabled(True) 163 | 164 | if __name__ == '__main__': 165 | app = QApplication(sys.argv) 166 | downloader = Downloader() 167 | downloader.show() 168 | sys.exit(app.exec_()) 169 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # Create a single executable file for the python application main.py 2 | # The executable file will be named Downloader.exe 3 | # The executable file will be created in the dist folder 4 | # The executable file will be created for the Windows operating system 5 | 6 | create_executable: 7 | pyinstaller --onefile --windowed --icon=download.ico main.py --name Downloader 8 | cp ./download.png ./dist/download.png 9 | cp ./download.ico ./dist/download.ico 10 | 11 | create_icon_from_png: 12 | convert -background transparent "download.png" -define icon:auto-resize=16,24,32,48,64,72,96,128,256 "download.ico" 13 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falleng0d/ai-downloader/18dde6c9f9f5fddfe6d09373f152db6b2035fd79/requirements.txt --------------------------------------------------------------------------------