├── .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 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 | 
14 |
15 | ## App icon
16 |
17 | The app icon was also made using AI. Specifically Stable Diffusion.
18 |
19 | 
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
--------------------------------------------------------------------------------