├── screens
├── sultans-tab.png
├── ui-screen.png
└── feather-chords.png
├── settings.cfg
├── .gitignore
├── requirements.txt
├── README.md
├── ui
├── results.ui
└── search.ui
└── src
├── utils.py
└── tab_scraper.py
/screens/sultans-tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seanfhear/tab-scraper/HEAD/screens/sultans-tab.png
--------------------------------------------------------------------------------
/screens/ui-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seanfhear/tab-scraper/HEAD/screens/ui-screen.png
--------------------------------------------------------------------------------
/screens/feather-chords.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seanfhear/tab-scraper/HEAD/screens/feather-chords.png
--------------------------------------------------------------------------------
/settings.cfg:
--------------------------------------------------------------------------------
1 | [MAIN]
2 |
3 | # the full path to the root directory you want to store all tabs in
4 | # e.g. = C:\Users\yourname\Music\Tabs\
5 | # make sure it ends with a slash
6 | destination_root = .
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | *.pyc
3 | vvenv/
4 | *.json
5 | *.log
6 | dist/
7 | src/geckodriver
8 | src/ui.py
9 | *.spec
10 | build/
11 | *.zip
12 | *.sh
13 | *.bat
14 | *.exe
15 | src/settings.cfg
16 | tab_scraper/
17 | release_notes.txt
18 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | cairocffi==1.0.2
2 | CairoSVG==2.5.1
3 | certifi==2019.3.9
4 | cffi==1.12.3
5 | chardet==3.0.4
6 | cssselect2==0.2.1
7 | defusedxml==0.6.0
8 | html5lib==1.0.1
9 | idna==2.8
10 | Pillow==9.0.0
11 | pycparser==2.19
12 | Pyphen==0.9.5
13 | PyQt5==5.12.1
14 | PyQt5-sip==4.19.15
15 | requests==2.21.0
16 | selenium==3.141.0
17 | six==1.12.0
18 | soupsieve==1.9.1
19 | tinycss2==1.0.2
20 | urllib3==1.26.5
21 | webencodings==0.5.1
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # tab-scraper
2 | An interface for downloading guitar tabs from Ultimate Guitar.
3 |
4 | 
5 |
6 | Get screenshots of Guitar Chords, Tabs, Bass Tabs and Ukulele Chords with no clutter.
7 |
8 | Chords | Tab
9 | :------:|:------|
10 |  | 
11 |
12 | You can also download GuitarPro and PowerTab files.
13 | All files are sorted into directories for quick and easy access.
14 |
15 | ### Prerequisites
16 |
17 | 1. Firefox Web Browser
18 |
19 | ### Running the Program
20 |
21 | #### Executable
22 |
23 | 1. Download an executable version [here](https://github.com/Sean-Hassett/tab-scraper/releases).
24 | 2. Follow instructions in the readme.txt provided.
25 |
26 | #### Command Line
27 |
28 | 1. Open settings.cfg and enter in the root directory where you would like all tabs to be stored e.g. username/Music/Tabs/
29 | 2. Download [Geckodriver](https://github.com/mozilla/geckodriver/releases) and put the geckodriver executable into the src directory.
30 | 3. Run `pip install -r requirements.txt`
31 | 4. run `python tab_scraper.py` from src directory.
32 |
33 | ### Built With
34 |
35 | - Python 3
36 |
37 | - [PyQT5](https://pypi.org/project/PyQt5/)
38 |
39 | - [Selenium](https://selenium-python.readthedocs.io/)
40 |
41 | - [Geckodriver](https://github.com/mozilla/geckodriver/releases)
42 |
--------------------------------------------------------------------------------
/ui/results.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Form
4 |
5 |
6 |
7 | 0
8 | 0
9 | 962
10 | 537
11 |
12 |
13 |
14 | Form
15 |
16 |
17 |
18 |
19 | 230
20 | 80
21 | 651
22 | 241
23 |
24 |
25 |
26 | true
27 |
28 |
29 | QAbstractItemView::SingleSelection
30 |
31 |
32 | QAbstractItemView::SelectRows
33 |
34 |
35 | QAbstractItemView::ScrollPerPixel
36 |
37 |
38 | QAbstractItemView::ScrollPerPixel
39 |
40 |
41 | false
42 |
43 |
44 | true
45 |
46 |
47 | 100
48 |
49 |
50 | 99
51 |
52 |
53 |
54 | 1
55 |
56 |
57 |
58 |
59 | Type
60 |
61 |
62 |
63 |
64 | Artist
65 |
66 |
67 |
68 |
69 | Title
70 |
71 |
72 |
73 |
74 | Rating
75 |
76 |
77 |
78 |
79 | Votes
80 |
81 |
82 | -
83 |
84 | Chords
85 |
86 |
87 | -
88 |
89 | Oasis
90 |
91 |
92 | -
93 |
94 | Wonderwall
95 |
96 |
97 | -
98 |
99 | 4.84
100 |
101 |
102 | -
103 |
104 | 3,277
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/ui/search.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 200
10 | 420
11 |
12 |
13 |
14 |
15 | 0
16 | 0
17 |
18 |
19 |
20 |
21 | 200
22 | 420
23 |
24 |
25 |
26 |
27 | 200
28 | 420
29 |
30 |
31 |
32 |
33 | 12
34 |
35 |
36 |
37 | MainWindow
38 |
39 |
40 | QTabWidget::Rounded
41 |
42 |
43 |
44 |
45 |
46 | 25
47 | 25
48 | 150
49 | 30
50 |
51 |
52 |
53 |
54 | 12
55 |
56 |
57 |
58 | false
59 |
60 |
61 |
62 |
63 |
64 | Artist
65 |
66 |
67 |
68 |
69 |
70 | 25
71 | 70
72 | 150
73 | 30
74 |
75 |
76 |
77 |
78 | 12
79 |
80 |
81 |
82 | Song Title
83 |
84 |
85 |
86 |
87 |
88 | 25
89 | 125
90 | 150
91 | 20
92 |
93 |
94 |
95 |
96 | 12
97 |
98 |
99 |
100 | Chords
101 |
102 |
103 |
104 |
105 |
106 | 25
107 | 160
108 | 150
109 | 20
110 |
111 |
112 |
113 |
114 | 12
115 |
116 |
117 |
118 | Tab
119 |
120 |
121 |
122 |
123 |
124 | 25
125 | 195
126 | 150
127 | 20
128 |
129 |
130 |
131 |
132 | 12
133 |
134 |
135 |
136 | GuitarPro
137 |
138 |
139 |
140 |
141 |
142 | 25
143 | 345
144 | 150
145 | 30
146 |
147 |
148 |
149 |
150 | 12
151 |
152 |
153 |
154 | Search
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 | 25
164 | 230
165 | 150
166 | 20
167 |
168 |
169 |
170 |
171 | 12
172 |
173 |
174 |
175 | Bass
176 |
177 |
178 |
179 |
180 |
181 | 25
182 | 265
183 | 150
184 | 20
185 |
186 |
187 |
188 |
189 | 12
190 |
191 |
192 |
193 | Ukulele
194 |
195 |
196 |
197 |
198 |
199 | 25
200 | 300
201 | 150
202 | 20
203 |
204 |
205 |
206 |
207 | 12
208 |
209 |
210 |
211 | Power
212 |
213 |
214 |
215 |
225 |
226 |
227 |
228 |
229 |
230 |
--------------------------------------------------------------------------------
/src/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import sys
4 | import json
5 | import requests
6 | from time import sleep
7 | from selenium import webdriver
8 | from selenium.webdriver.firefox.options import Options
9 | from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
10 | from selenium.common.exceptions import NoSuchElementException
11 | from configparser import ConfigParser
12 |
13 |
14 | SEARCH_URL = "https://www.ultimate-guitar.com/search.php?page={}&search_type=title&value={}"
15 | RESULTS_PATTERN = "\"results\":(\[.*?\]),\"pagination\""
16 | RESULTS_COUNT_PATTERN = "\"tabs\",\"results_count\":([0-9]+?),\"results\""
17 | DOWNLOAD_TIMEOUT = 15
18 |
19 |
20 | def search_tabs(search_string, types):
21 | page = 1
22 | # get first page of results
23 | response = requests.get(SEARCH_URL.format(page, search_string))
24 | # count is the number of results, used to know how many pages to search
25 | count = 0
26 | try:
27 | # isolate results from page using regex
28 | response_body = response.content.decode()
29 | results = re.search(RESULTS_PATTERN, response_body).group(1)
30 | count = int(re.search(RESULTS_COUNT_PATTERN, response_body).group(1))
31 | except AttributeError:
32 | results = ''
33 | response_data = json.loads(results)
34 |
35 | ret = []
36 | while count > 0:
37 | for item in response_data:
38 | try:
39 | # Get every result that has a desired type
40 | if item["type"] in types:
41 | ret.append((item["type"], item["artist_name"], item["song_name"],
42 | str(round(float(item["rating"]), 1)), str(item["votes"]),
43 | item["tab_url"], str(item["version"])))
44 | except KeyError:
45 | # key error on "official" tabs, not interested in these tabs
46 | ''
47 | count -= 1
48 | if count > 0:
49 | page += 1
50 | response = requests.get(SEARCH_URL.format(page, search_string))
51 | try:
52 | # isolate results from page using regex
53 | response_body = response.content.decode()
54 | results = re.search(RESULTS_PATTERN, response_body).group(1)
55 | except AttributeError:
56 | results = ''
57 | response_data = json.loads(results)
58 | return ret
59 |
60 |
61 | def download_tab(url, tab_type, artist, title, version):
62 | if getattr(sys, 'frozen', False):
63 | # If the application is run as a bundle, the pyInstaller bootloader
64 | # extends the sys module by a flag frozen=True
65 | application_path = os.path.dirname(sys.executable)
66 | else:
67 | application_path = os.path.dirname(os.path.abspath(os.path.splitext(__file__)[0]))
68 | settings_file = os.path.join(application_path, "settings.cfg")
69 |
70 | config = ConfigParser()
71 | config.read(settings_file)
72 | cfg = config['MAIN']
73 |
74 | # get path to gecko executable by joining the application path with 'geckodriver' and the .exe file extension
75 | # if the executed tab_scraper is an exe
76 | gecko_path = (os.path.join(application_path, "geckodriver",
77 | ".exe" if os.path.splitext(__file__)[1] == ".exe" else ""))[:-1]
78 |
79 | # create destination directory if it doesn't exist
80 | destination_root = cfg['destination_root']
81 | destination = os.path.join(destination_root, tab_type, artist)
82 | os.makedirs(destination, exist_ok=True)
83 |
84 | options = Options()
85 | options.headless = True
86 |
87 | driver = webdriver.Firefox(options=options, executable_path=gecko_path)
88 | driver.get(url)
89 |
90 | # clear the privacy policy message
91 | try:
92 | popup_btn = driver.find_element_by_xpath('//button[text()="Got it, thanks!"]')
93 | popup_btn.click()
94 | except NoSuchElementException:
95 | pass
96 |
97 | # clear the official tab ad
98 | try:
99 | popup_btn = driver.find_element_by_xpath('//div[contains(@class, "ai-ah")]//button')
100 | popup_btn.click()
101 | except NoSuchElementException:
102 | pass
103 |
104 | # hide the autoscroll tool
105 | try:
106 | autoscroll = driver.find_element_by_xpath('//span[text()="Autoscroll"]/parent::button/parent::div/parent::section')
107 | driver.execute_script("arguments[0].setAttribute('style', 'display: none')", autoscroll)
108 | except NoSuchElementException:
109 | pass
110 |
111 | tab = driver.find_element_by_tag_name("pre")
112 | filename = os.path.join(destination, (title + " (Ver " + version + ")" + ".png"))
113 | tab.screenshot(filename)
114 |
115 | driver.quit()
116 |
117 |
118 | def download_file(url, tab_type, artist):
119 | if getattr(sys, 'frozen', False):
120 | # If the application is run as a bundle, the pyInstaller bootloader
121 | # extends the sys module by a flag frozen=True
122 | application_path = os.path.dirname(sys.executable)
123 | else:
124 | application_path = os.path.dirname(os.path.abspath(os.path.splitext(__file__)[0]))
125 | settings_file = os.path.join(application_path, "settings.cfg")
126 | config = ConfigParser()
127 | config.read(settings_file)
128 | cfg = config['MAIN']
129 |
130 | # get path to gecko executable by joining the application path with 'geckodriver' and the .exe file extension
131 | # if the executed tab_scraper is an exe
132 | gecko_path = (os.path.join(application_path, "geckodriver",
133 | ".exe" if os.path.splitext(__file__)[1] == ".exe" else ""))[:-1]
134 |
135 | # create destination directory if it doesn't exist
136 | destination_root = cfg['destination_root']
137 | destination = os.path.join(destination_root, tab_type, artist)
138 | os.makedirs(destination, exist_ok=True)
139 |
140 | # count how many files are in the destination already
141 | nFiles = len(os.listdir(destination))
142 |
143 | options = Options()
144 | options.headless = True
145 |
146 | profile = FirefoxProfile()
147 | profile.set_preference("browser.download.folderList", 2)
148 | profile.set_preference("browser.download.manager.showWhenStarting", False)
149 | profile.set_preference("browser.download.dir", destination)
150 | profile.set_preference("browser.helperApps.neverAsk.saveToDisk", "application/octet-stream")
151 |
152 | driver = webdriver.Firefox(options=options, firefox_profile=profile, executable_path=gecko_path)
153 | driver.get(url)
154 | button = driver.find_element_by_xpath('//button/span[text()="DOWNLOAD Guitar Pro TAB" '
155 | 'or text()="DOWNLOAD Power TAB"]')
156 | driver.execute_script("arguments[0].click();", button)
157 |
158 | # kill firefox process after download completes or a timeout is reached
159 | downloading = True
160 | timeout = DOWNLOAD_TIMEOUT
161 | while downloading and timeout > 0:
162 | sleep(0.5)
163 | if len(os.listdir(destination)) > nFiles:
164 | downloading = False
165 | timeout -= 0.5
166 | driver.quit()
167 |
--------------------------------------------------------------------------------
/src/tab_scraper.py:
--------------------------------------------------------------------------------
1 | from PyQt5 import QtCore, QtGui, QtWidgets
2 | from src import utils
3 | import sys
4 | import os
5 | from configparser import ConfigParser
6 |
7 | VERSION = 'v.0.1.2'
8 | TOTAL_WIDTH = 1000
9 | SEARCH_WIDTH = 200
10 | SEARCH_ELEMENT_WIDTH = 150
11 | TEXT_BOX_HEIGHT = 30
12 | CHECK_BOX_HEIGHT = 20
13 | BUTTON_HEIGHT = 30
14 | BUTTON_WIDTH = 150
15 | DIRECTORY_BUTTON_WIDTH = 30
16 | OFFSET = 25
17 | CHECK_BOX_OFFSET = 15
18 | CHECK_BOX_NAMES = ["{}Chords", "{}Tab", "{}GuitarPro", "{}PowerTab", "{}Bass", "{}Ukulele"]
19 | TYPES_DICT = {"Chords": "Chords",
20 | "Tab": "Tabs",
21 | "GuitarPro": "Pro",
22 | "PowerTab": "Power",
23 | "Bass": "Bass Tabs",
24 | "Ukulele": "Ukulele Chords"}
25 | TABLE_COLUMNS = ["Type", "Artist", "Title", "Rating", "Votes"]
26 |
27 |
28 | class MainWindow(object):
29 | def setup_ui(self, search_window):
30 | self.status = ''
31 | self.results = []
32 | font = QtGui.QFont()
33 | font.setPointSize(12)
34 |
35 | window_height = ((OFFSET * 2 + TEXT_BOX_HEIGHT) +
36 | ((CHECK_BOX_HEIGHT + CHECK_BOX_OFFSET) * len(CHECK_BOX_NAMES) - CHECK_BOX_OFFSET) +
37 | (OFFSET * 2 + BUTTON_HEIGHT)) * 2
38 |
39 | search_window.setObjectName("SearchWindow")
40 | search_window.setWindowTitle("Tab Scraper " + VERSION)
41 | search_window.setMinimumSize(QtCore.QSize(TOTAL_WIDTH, window_height))
42 | search_window.setMaximumSize(QtCore.QSize(TOTAL_WIDTH, window_height))
43 | search_window.setFont(font)
44 | search_window.setTabShape(QtWidgets.QTabWidget.Rounded)
45 |
46 | self.central_widget = QtWidgets.QWidget(search_window)
47 | self.central_widget.setObjectName("centralwidget")
48 |
49 | self.check_boxes = [" "] * len(CHECK_BOX_NAMES)
50 | for i, name in enumerate(CHECK_BOX_NAMES):
51 | self.check_boxes[i] = QtWidgets.QCheckBox(self.central_widget)
52 | self.check_boxes[i].setGeometry(QtCore.QRect(OFFSET,
53 | ((OFFSET * 2 + TEXT_BOX_HEIGHT) +
54 | (CHECK_BOX_HEIGHT + CHECK_BOX_OFFSET) * i),
55 | SEARCH_ELEMENT_WIDTH,
56 | CHECK_BOX_HEIGHT))
57 | self.check_boxes[i].setObjectName(name.format("checkBox"))
58 | self.check_boxes[i].setText(name.format(""))
59 |
60 | self.search_button = QtWidgets.QPushButton(self.central_widget)
61 | self.search_button.setGeometry(QtCore.QRect(OFFSET,
62 | ((OFFSET * 2 + TEXT_BOX_HEIGHT) +
63 | (CHECK_BOX_HEIGHT + CHECK_BOX_OFFSET) *
64 | len(CHECK_BOX_NAMES) - CHECK_BOX_OFFSET + OFFSET),
65 | BUTTON_WIDTH,
66 | BUTTON_HEIGHT))
67 | self.search_button.setObjectName("searchButton")
68 | self.search_button.setText("Search")
69 | self.search_button.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
70 | self.search_button.clicked.connect(self.search_tabs)
71 |
72 | self.download_button = QtWidgets.QPushButton(self.central_widget)
73 | self.download_button.setGeometry(QtCore.QRect(OFFSET,
74 | ((OFFSET * 2 + TEXT_BOX_HEIGHT) +
75 | (CHECK_BOX_HEIGHT + CHECK_BOX_OFFSET) *
76 | len(CHECK_BOX_NAMES) - CHECK_BOX_OFFSET + OFFSET +
77 | (OFFSET + BUTTON_HEIGHT)),
78 | BUTTON_WIDTH - DIRECTORY_BUTTON_WIDTH,
79 | BUTTON_HEIGHT))
80 | self.download_button.setObjectName("downloadButton")
81 | self.download_button.setText("Download")
82 | self.download_button.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
83 | self.download_button.clicked.connect(self.download_tab)
84 |
85 | self.set_directory_button = QtWidgets.QPushButton(self.central_widget)
86 | self.set_directory_button.setGeometry(QtCore.QRect(OFFSET + (BUTTON_WIDTH - DIRECTORY_BUTTON_WIDTH),
87 | ((OFFSET * 2 + TEXT_BOX_HEIGHT) +
88 | (CHECK_BOX_HEIGHT + CHECK_BOX_OFFSET) *
89 | len(CHECK_BOX_NAMES) - CHECK_BOX_OFFSET + OFFSET +
90 | (OFFSET + BUTTON_HEIGHT)),
91 | DIRECTORY_BUTTON_WIDTH,
92 | BUTTON_HEIGHT))
93 | self.set_directory_button.setObjectName("downloadButton")
94 | self.set_directory_button.setText("...")
95 | self.set_directory_button.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
96 | self.set_directory_button.clicked.connect(self.set_download_location)
97 |
98 | self.status_message = QtWidgets.QLabel(self.central_widget)
99 | self.status_message.setGeometry(QtCore.QRect(OFFSET,
100 | ((OFFSET * 2 + TEXT_BOX_HEIGHT) +
101 | (CHECK_BOX_HEIGHT + CHECK_BOX_OFFSET) *
102 | len(CHECK_BOX_NAMES) - CHECK_BOX_OFFSET + OFFSET +
103 | (OFFSET + BUTTON_HEIGHT) * 2),
104 | BUTTON_WIDTH,
105 | BUTTON_HEIGHT))
106 | status_font = QtGui.QFont()
107 | status_font.setPointSize(10)
108 | self.status_message.setFont(status_font)
109 | self.status_message.setText(self.status)
110 |
111 |
112 | self.search_input = QtWidgets.QLineEdit(self.central_widget)
113 | self.search_input.setGeometry(QtCore.QRect(OFFSET, OFFSET, SEARCH_ELEMENT_WIDTH, TEXT_BOX_HEIGHT))
114 | self.search_input.setObjectName("lineEdit")
115 | self.search_input.returnPressed.connect(self.search_button.click)
116 | self.search_input.setPlaceholderText("Search query...")
117 |
118 | self.tableWidget = QtWidgets.QTableWidget(self.central_widget)
119 | self.tableWidget.setGeometry(QtCore.QRect(SEARCH_WIDTH, OFFSET, TOTAL_WIDTH - SEARCH_WIDTH - OFFSET,
120 | window_height - OFFSET * 2))
121 | self.tableWidget.setSortingEnabled(False)
122 | self.tableWidget.setAlternatingRowColors(True)
123 | self.tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
124 | self.tableWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
125 | self.tableWidget.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
126 | self.tableWidget.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
127 | self.tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
128 |
129 | self.tableWidget.setShowGrid(False)
130 | self.tableWidget.setObjectName("tableWidget")
131 | self.tableWidget.setColumnCount(len(TABLE_COLUMNS))
132 | self.tableWidget.setRowCount(len(self.results))
133 |
134 | for i, header in enumerate(TABLE_COLUMNS):
135 | item = QtWidgets.QTableWidgetItem()
136 | item.setText(header)
137 | self.tableWidget.setHorizontalHeaderItem(i, item)
138 |
139 | header = self.tableWidget.horizontalHeader()
140 | for i in range(len(TABLE_COLUMNS)):
141 | if TABLE_COLUMNS[i] == "Title":
142 | header.setSectionResizeMode(i, QtWidgets.QHeaderView.Stretch)
143 | else:
144 | header.setSectionResizeMode(i, QtWidgets.QHeaderView.ResizeToContents)
145 |
146 | self.tableWidget.horizontalHeader().setDefaultSectionSize(100)
147 | self.tableWidget.horizontalHeader().setMinimumSectionSize(100)
148 |
149 | QtCore.QMetaObject.connectSlotsByName(search_window)
150 | search_window.setCentralWidget(self.central_widget)
151 |
152 | def search_tabs(self):
153 | types = []
154 | for check_box in self.check_boxes:
155 | if check_box.isChecked():
156 | types.append(TYPES_DICT[check_box.text()])
157 | search_string = "%20".join(self.search_input.text().split())
158 | if len(search_string) > 0:
159 | self.results = utils.search_tabs(search_string, types)
160 | self.update_table()
161 |
162 | def update_table(self):
163 | self.tableWidget.setRowCount(len(self.results))
164 | for i in range(len(self.results)):
165 | for j in range(len(TABLE_COLUMNS)):
166 | item = QtWidgets.QTableWidgetItem()
167 | item.setText(self.results[i][j])
168 | self.tableWidget.setItem(i, j, item)
169 |
170 | def download_tab(self):
171 | self.status_message.setText('Downloading...')
172 | self.status_message.repaint()
173 | if len(self.results) > 0:
174 | row = self.results[self.tableWidget.currentRow()]
175 | url = row[-2]
176 | is_file = False
177 | if row[0] == "Pro" or row[0] == "Power":
178 | is_file = True
179 |
180 | if is_file:
181 | utils.download_file(url, row[0], row[1].replace("/", ""))
182 | else:
183 | utils.download_tab(url, row[0], row[1].replace("/", ""), row[2], row[6])
184 | self.status_message.setText('Download finished')
185 | #self.central_widget.update()
186 |
187 | def set_download_location(self):
188 | dialog = QtWidgets.QFileDialog()
189 | folder_path = dialog.getExistingDirectory(None, "Select Folder")
190 |
191 | if getattr(sys, 'frozen', False):
192 | # If the application is run as a bundle, the pyInstaller bootloader
193 | # extends the sys module by a flag frozen=True
194 | application_path = os.path.dirname(sys.executable)
195 | else:
196 | application_path = os.path.dirname(os.path.abspath(os.path.splitext(__file__)[0]))
197 | settings_file = os.path.join(application_path, "settings.cfg")
198 |
199 | config = ConfigParser()
200 | config.read(settings_file)
201 |
202 | config.set('MAIN', 'destination_root', folder_path)
203 | with open(settings_file, 'w+') as f:
204 | config.write(f)
205 |
206 |
207 |
208 |
209 | if __name__ == "__main__":
210 | app = QtWidgets.QApplication(sys.argv)
211 | SearchWindow = QtWidgets.QMainWindow()
212 | ui = MainWindow()
213 | ui.setup_ui(SearchWindow)
214 | SearchWindow.show()
215 | sys.exit(app.exec_())
216 |
--------------------------------------------------------------------------------