diff --git a/README.md b/README.md index cdee88d9..fa04dcc9 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ Using python3 and PyQt5 ## 2. Key features -+ Supported Search Engine: Google, Bing, Baidu ++ Supported Search Engine: Google, Bing, Baidu, Unsplash + Keywords input from keyboard, or input from line seperated keywords list file for batch process. + Download image using customizable number of threads. -+ Fully supported conditional search (eg. filetype:, site:). ++ Fully supported conditional search (eg. filetype:, site:, exactsize:). + Switch for Google safe mode. + Proxy configuration (socks, http). + CMD and GUI ways of using are provided. @@ -30,6 +30,8 @@ Using python3 and PyQt5 + Require Google Chrome Browser or Chromium Browser installed. + Download the corresponding version of chromedriver from [here](https://chromedriver.chromium.org/downloads) + Copy `chromedriver` binary to ${project_directory}/bin/ or add it to PATH. ++ Download the corresponding version of geckodriver from [here](https://github.com/mozilla/geckodriver/releases) ++ Copy `geckodriver` binary to ${project_directory}/bin/ or add it to PATH. ### 3.3 Download and setup phantomjs [deprecated] @@ -56,13 +58,14 @@ python image_downloader_gui.py ### 4.2 CMD ```bash -usage: image_downloader.py [-h] [--engine {Google,Bing,Baidu}] +usage: image_downloader.py [-h] [--engine {Google,Bing,Baidu,Unsplash}] [--driver {chrome_headless,chrome,phantomjs}] [--max-number MAX_NUMBER] [--num-threads NUM_THREADS] [--timeout TIMEOUT] [--output OUTPUT] [--safe-mode] [--face-only] - [--proxy_http PROXY_HTTP] - [--proxy_socks5 PROXY_SOCKS5] + [--proxy_http PROXY_HTTP] [--exact-size EXACT_SIZE] + [--proxy_socks5 PROXY_SOCKS5] [--specific-site SPECIFIC_SITE] + [--color COLOR] keywords ``` diff --git a/crawler.py b/crawler.py index 6e853502..49bc38f3 100644 --- a/crawler.py +++ b/crawler.py @@ -35,29 +35,37 @@ def my_print(msg, quiet=False): print(msg) -def google_gen_query_url(keywords, face_only=False, safe_mode=False, image_type=None, color=None): +def google_gen_query_url(keywords, face_only=False, safe_mode=False, image_type=None, color=None, exact_size=None, + specific_site=None): + filter_url = "" base_url = "https://www.google.com/search?tbm=isch&hl=en" - keywords_str = "&q=" + quote(keywords) - query_url = base_url + keywords_str - - if safe_mode is True: - query_url += "&safe=on" + if specific_site is not None or specific_site == "unsplash": + query_url = "https://unsplash.com/s/photos/" + keywords else: - query_url += "&safe=off" - - filter_url = "&tbs=" + keywords_str = "&q=" + quote(keywords) + query_url = base_url + keywords_str + filter_url = "&tbs=" + + if exact_size is not None: + query_url += " " + quote(exact_size) + + if specific_site is None: + if safe_mode is True: + query_url += "&safe=on" + else: + query_url += "&safe=off" if color is not None: if color == "bw": filter_url += "ic:gray%2C" else: filter_url += "ic:specific%2Cisc:{}%2C".format(color.lower()) - + if image_type is not None: if image_type.lower() == "linedrawing": image_type = "lineart" filter_url += "itp:{}".format(image_type) - + if face_only is True: filter_url += "itp:face" @@ -87,7 +95,7 @@ def google_image_url_from_webpage(driver, max_number, quiet=False): except Exception as e: print("Exception ", e) pass - + if len(thumb_elements) == 0: return [] @@ -106,7 +114,7 @@ def google_image_url_from_webpage(driver, max_number, quiet=False): print("Error while clicking in thumbnail:", e) retry_click.append(elem) - if len(retry_click) > 0: + if len(retry_click) > 0: my_print("Retry some failed clicks ...", quiet) for elem in retry_click: try: @@ -114,7 +122,7 @@ def google_image_url_from_webpage(driver, max_number, quiet=False): elem.click() except Exception as e: print("Error while retrying click:", e) - + image_elements = driver.find_elements_by_class_name("islib") image_urls = list() url_pattern = r"imgurl=\S*&imgrefurl" @@ -128,17 +136,20 @@ def google_image_url_from_webpage(driver, max_number, quiet=False): return image_urls -def bing_gen_query_url(keywords, face_only=False, safe_mode=False, image_type=None, color=None): +def bing_gen_query_url(keywords, face_only=False, safe_mode=False, image_type=None, color=None, exact_size=None): base_url = "https://www.bing.com/images/search?" keywords_str = "&q=" + quote(keywords) query_url = base_url + keywords_str filter_url = "&qft=" if face_only is True: filter_url += "+filterui:face-face" - + + if exact_size is not None: + query_url += " " + quote(exact_size) + if image_type is not None: filter_url += "+filterui:photo-{}".format(image_type) - + if color is not None: if color == "bw" or color == "color": filter_url += "+filterui:color2-{}".format(color.lower()) @@ -181,12 +192,15 @@ def bing_image_url_from_webpage(driver): "yellow": 2, "purple": 32, "green": 4, "teal": 8, "orange": 256, "brown": 128 } -def baidu_gen_query_url(keywords, face_only=False, safe_mode=False, color=None): + +def baidu_gen_query_url(keywords, face_only=False, safe_mode=False, color=None, exact_size=None): base_url = "https://image.baidu.com/search/index?tn=baiduimage" keywords_str = "&word=" + quote(keywords) query_url = base_url + keywords_str if face_only is True: query_url += "&face=1" + if exact_size is not None: + query_url += " " + quote(exact_size) if color is not None: print(color, baidu_color_code[color.lower()]) if color is not None: @@ -217,7 +231,7 @@ def decode_url(url): url = url.replace(k, v) return url.translate(translate_table) - base_url = "https://image.baidu.com/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592"\ + base_url = "https://image.baidu.com/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592" \ "&lm=7&fp=result&ie=utf-8&oe=utf-8&st=-1" keywords_str = "&word={}&queryWord={}".format( quote(keywords), quote(keywords)) @@ -263,7 +277,7 @@ def decode_url(url): def process_batch(batch_no, batch_size): image_urls = list() url = query_url + \ - "&pn={}&rn={}".format(batch_no * batch_size, batch_size) + "&pn={}&rn={}".format(batch_no * batch_size, batch_size) try_time = 0 while True: try: @@ -295,11 +309,51 @@ def process_batch(batch_no, batch_size): return crawled_urls[:min(len(crawled_urls), target_num)] -def crawl_image_urls(keywords, engine="Google", max_number=10000, - face_only=False, safe_mode=False, proxy=None, - proxy_type="http", quiet=False, browser="phantomjs", image_type=None, color=None): +def unsplash_image_url_from_webpage(driver, max_number): + image_urls = [] + new_element = [] + count = 0 + element = [] + + print("downloading unsplash images...: ", max_number) + while True: + try: + element = driver.find_elements_by_tag_name('a') and \ + driver.find_elements_by_xpath('//*[@title="Download photo"]') + count += len(element) + new_element.extend(element) + if len(new_element) >= max_number: + break + if count >= max_number: + break + driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") + time.sleep(2) + except Exception as e: + print("Error gathering image ", e) + pass + + count = 0 + + for elem in new_element[:max_number]: + try: + count += 1 + image_url = elem.get_attribute('href') + image_urls.append(image_url) + except Exception as e: + print("Download Error: ", e) + pass + + return image_urls + + +def crawl_image_urls(keywords, engine="Google", max_number=100, + face_only=False, safe_mode=False, proxy=None, + proxy_type="http", quiet=False, browser="phantomjs", image_type=None, color=None, exact_size=None, + specific_site=None): """ Scrape image urls of keywords from Google Image Search + :param specific_site: unsplash search engine for images + :param exact_size: add to keyword of exact size :param keywords: keywords you want to search :param engine: search engine used to search images :param max_number: limit the max number of image urls the function output, equal or less than 0 for unlimited @@ -307,7 +361,7 @@ def crawl_image_urls(keywords, engine="Google", max_number=10000, :param safe_mode: switch for safe mode of Google Search :param proxy: proxy address, example: socks5 127.0.0.1:1080 :param proxy_type: socks5, http - :param browser: browser to use when crawl image urls from Google & Bing + :param browser: browser to use when crawl image urls from Google & Bing :return: list of scraped image urls """ @@ -315,18 +369,21 @@ def crawl_image_urls(keywords, engine="Google", max_number=10000, my_print("Keywords: " + keywords, quiet) if max_number <= 0: my_print("Number: No limit", quiet) - max_number = 10000 + max_number = 100 else: my_print("Number: {}".format(max_number), quiet) my_print("Face Only: {}".format(str(face_only)), quiet) my_print("Safe Mode: {}".format(str(safe_mode)), quiet) if engine == "Google": - query_url = google_gen_query_url(keywords, face_only, safe_mode, image_type, color) + query_url = google_gen_query_url(keywords, face_only, safe_mode, image_type, color, exact_size) elif engine == "Bing": - query_url = bing_gen_query_url(keywords, face_only, safe_mode, image_type, color) + query_url = bing_gen_query_url(keywords, face_only, safe_mode, image_type, color, exact_size) elif engine == "Baidu": - query_url = baidu_gen_query_url(keywords, face_only, safe_mode, color) + query_url = baidu_gen_query_url(keywords, face_only, safe_mode, color, exact_size) + elif engine == "Unsplash": + query_url = google_gen_query_url(keywords, face_only, safe_mode, image_type, color, exact_size, + specific_site="Unsplash") else: return @@ -334,7 +391,17 @@ def crawl_image_urls(keywords, engine="Google", max_number=10000, if engine != "Baidu": browser = str.lower(browser) - if "chrome" in browser: + if "firefox" in browser: + firefox_path = shutil.which("geckodriver") + firefox_path = "./bin/geckodriver" if firefox_path is None else firefox_path + firefox_options = webdriver.FirefoxOptions() + if "headless" in browser: + firefox_options.add_argument("headless") + if proxy is not None and proxy_type is not None: + firefox_options.add_argument("--proxy-server={}://{}".format(proxy_type, proxy)) + print('Firefox path: ' + firefox_path) + driver = webdriver.Firefox(executable_path=firefox_path, firefox_options=firefox_options) + elif "chrome" in browser: chrome_path = shutil.which("chromedriver") chrome_path = "./bin/chromedriver" if chrome_path is None else chrome_path chrome_options = webdriver.ChromeOptions() @@ -353,22 +420,32 @@ def crawl_image_urls(keywords, engine="Google", max_number=10000, "--proxy-type=" + proxy_type, ] driver = webdriver.PhantomJS(executable_path=phantomjs_path, - service_args=phantomjs_args, desired_capabilities=dcap) - - if engine == "Google": - driver.set_window_size(1920, 1080) - driver.get(query_url) - image_urls = google_image_url_from_webpage(driver, max_number, quiet) - elif engine == "Bing": + service_args=phantomjs_args, desired_capabilities=dcap) + + if specific_site is None: + if engine == "Google": + driver.set_window_size(1920, 1080) + driver.get(query_url) + image_urls = google_image_url_from_webpage(driver, max_number, quiet) + elif engine == "Bing": + driver.set_window_size(1920, 1080) + driver.get(query_url) + image_urls = bing_image_url_from_webpage(driver) + elif engine == "Unsplash": + driver.set_window_size(1920, 1080) + driver.get(query_url) + image_urls = unsplash_image_url_from_webpage(driver, max_number) + else: # Baidu + # driver.set_window_size(10000, 7500) + # driver.get(query_url) + # image_urls = baidu_image_url_from_webpage(driver) + image_urls = baidu_get_image_url_using_api(keywords, max_number=max_number, face_only=face_only, + proxy=proxy, proxy_type=proxy_type) + else: driver.set_window_size(1920, 1080) driver.get(query_url) - image_urls = bing_image_url_from_webpage(driver) - else: # Baidu - # driver.set_window_size(10000, 7500) - # driver.get(query_url) - # image_urls = baidu_image_url_from_webpage(driver) - image_urls = baidu_get_image_url_using_api(keywords, max_number=max_number, face_only=face_only, - proxy=proxy, proxy_type=proxy_type) + image_urls = unsplash_image_url_from_webpage(driver, max_number) + if engine != "Baidu": driver.close() diff --git a/image_downloader.py b/image_downloader.py index 601cf536..212c04fe 100644 --- a/image_downloader.py +++ b/image_downloader.py @@ -16,9 +16,9 @@ def main(argv): parser.add_argument("keywords", type=str, help='Keywords to search. ("in quotes")') parser.add_argument("--engine", "-e", type=str, default="Google", - help="Image search engine.", choices=["Google", "Bing", "Baidu"]) + help="Image search engine.", choices=["Google", "Bing", "Baidu", "Unsplash"]) parser.add_argument("--driver", "-d", type=str, default="chrome_headless", - help="Image search engine.", choices=["chrome_headless", "chrome", "phantomjs"]) + help="Image search engine.", choices=["chrome_headless", "chrome", "phantomjs", "firefox"]) parser.add_argument("--max-number", "-n", type=int, default=100, help="Max number of images download for the keywords.") parser.add_argument("--num-threads", "-j", type=int, default=50, @@ -38,6 +38,10 @@ def main(argv): # type is not supported for Baidu parser.add_argument("--type", "-ty", type=str, default=None, help="What kinds of images to download.", choices=["clipart", "linedrawing", "photograph"]) + parser.add_argument('--exact-size', '-es', help='exact image resolution "WIDTHxHEIGHT', + type=str, required=False, default=None) + parser.add_argument('--specific-site', '-ss', help='Search for image in specific site', + type=str, required=False, default=None) # Bing: color for colored images, bw for black&white images, other color contains Red, orange, yellow, green # Teal, Blue, Purple, Pink, Brown, Black, Gray, White # Baidu: white, bw, black, pink, blue, red, yellow, purple, green, teal, orange, brown @@ -60,7 +64,8 @@ def main(argv): engine=args.engine, max_number=args.max_number, face_only=args.face_only, safe_mode=args.safe_mode, proxy_type=proxy_type, proxy=proxy, - browser=args.driver, image_type=args.type, color=args.color) + browser=args.driver, image_type=args.type, color=args.color, + exact_size=args.exact_size, specific_site=args.specific_site) downloader.download_images(image_urls=crawled_urls, dst_dir=args.output, concurrency=args.num_threads, timeout=args.timeout, proxy_type=proxy_type, proxy=proxy, diff --git a/mainwindow.py b/mainwindow.py index 086f63da..738867ff 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -102,12 +102,16 @@ def gen_config_from_ui(self): config.engine = "Bing" elif self.radioButton_baidu.isChecked(): config.engine = "Baidu" + elif self.radioButton_unsplash.isChecked(): + config.engine = "Unsplash" """ Driver """ if self.radioButton_chrome_headless.isChecked(): config.driver = "chrome_headless" elif self.radioButton_chrome.isChecked(): config.driver = "chrome" + elif self.radioButton_firefox.isChecked(): + config.driver = "firefox" elif self.radioButton_phantomjs.isChecked(): config.driver = "phantomjs" @@ -122,6 +126,10 @@ def gen_config_from_ui(self): config.max_number = self.spinBox_max_number.value() config.num_threads = self.spinBox_num_threads.value() + """ Resolution """ + if self.lineEdit_resolution.text() != "": + config.resolution = self.lineEdit_resolution.text() + """ Proxy """ if self.checkBox_proxy.isChecked(): if self.radioButton_http.isChecked(): diff --git a/requirements.txt b/requirements.txt index 6fabaa5b..16e9ffe1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ future PySocks requests selenium -PyQt5 +PyQt diff --git a/ui_mainwindow.py b/ui_mainwindow.py index c85ad8eb..559d440a 100644 --- a/ui_mainwindow.py +++ b/ui_mainwindow.py @@ -200,6 +200,14 @@ def setupUi(self, MainWindow): self.widget_driver.setObjectName("widget_driver") self.horizontalLayout_6 = QtWidgets.QHBoxLayout(self.widget_driver) self.horizontalLayout_6.setObjectName("horizontalLayout_6") + self.radioButton_unsplash = QtWidgets.QRadioButton(self.widget_engine) + font = QtGui.QFont() + font.setPointSize(12) + self.radioButton_unsplash.setFont(font) + self.radioButton_unsplash.setFocusPolicy(QtCore.Qt.StrongFocus) + self.radioButton_unsplash.setObjectName("radioButton_unsplash") + self.buttonGroup.addButton(self.radioButton_unsplash) + self.horizontalLayout.addWidget(self.radioButton_unsplash) self.radioButton_chrome_headless = QtWidgets.QRadioButton(self.widget_driver) font = QtGui.QFont() font.setPointSize(12) @@ -213,6 +221,12 @@ def setupUi(self, MainWindow): self.radioButton_chrome.setFont(font) self.radioButton_chrome.setObjectName("radioButton_chrome") self.horizontalLayout_6.addWidget(self.radioButton_chrome) + self.radioButton_firefox = QtWidgets.QRadioButton(self.widget_driver) + font = QtGui.QFont() + font.setPointSize(12) + self.radioButton_firefox.setFont(font) + self.radioButton_firefox.setObjectName("radioButton_firefox") + self.horizontalLayout_6.addWidget(self.radioButton_firefox) self.radioButton_phantomjs = QtWidgets.QRadioButton(self.widget_driver) font = QtGui.QFont() font.setPointSize(12) @@ -252,14 +266,14 @@ def setupUi(self, MainWindow): font.setUnderline(False) self.checkBox_from_file.setFont(font) self.checkBox_from_file.setObjectName("checkBox_from_file") - self.gridLayout.addWidget(self.checkBox_from_file, 1, 0, 1, 1) + self.gridLayout.addWidget(self.checkBox_from_file, 2, 0, 1, 1) self.lineEdit_path2file = QtWidgets.QLineEdit(self.widget_keywords) self.lineEdit_path2file.setEnabled(False) font = QtGui.QFont() font.setPointSize(12) self.lineEdit_path2file.setFont(font) self.lineEdit_path2file.setObjectName("lineEdit_path2file") - self.gridLayout.addWidget(self.lineEdit_path2file, 1, 1, 1, 1) + self.gridLayout.addWidget(self.lineEdit_path2file, 2, 1, 1, 1) self.pushButton_load_file = QtWidgets.QPushButton(self.widget_keywords) self.pushButton_load_file.setEnabled(False) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) @@ -271,7 +285,26 @@ def setupUi(self, MainWindow): font.setPointSize(12) self.pushButton_load_file.setFont(font) self.pushButton_load_file.setObjectName("pushButton_load_file") - self.gridLayout.addWidget(self.pushButton_load_file, 1, 2, 1, 1) + self.gridLayout.addWidget(self.pushButton_load_file, 2, 2, 1, 1) + self.label_8 = QtWidgets.QLabel(self.widget_keywords) + self.label_8.setEnabled(True) + font = QtGui.QFont() + font.setPointSize(12) + font.setBold(True) + font.setWeight(75) + self.label_8.setFont(font) + self.label_8.setLineWidth(0) + self.label_8.setScaledContents(False) + self.label_8.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) + self.label_8.setObjectName("label_8") + self.gridLayout.addWidget(self.label_8, 1, 0, 1, 1) + self.lineEdit_resolution = QtWidgets.QLineEdit(self.widget_keywords) + font = QtGui.QFont() + font.setPointSize(12) + self.lineEdit_resolution.setFont(font) + self.lineEdit_resolution.setAcceptDrops(True) + self.lineEdit_resolution.setObjectName("lineEdit_resolution") + self.gridLayout.addWidget(self.lineEdit_resolution, 1, 1, 1, 2) self.gridLayout.setColumnStretch(0, 2) self.gridLayout.setColumnStretch(1, 6) self.gridLayout.setColumnStretch(2, 1) @@ -496,10 +529,10 @@ def setupUi(self, MainWindow): self.menuAbout.addAction(self.actionAbout) self.menubar.addAction(self.menuAbout.menuAction()) self.label.setBuddy(self.lineEdit_keywords) + self.label_8.setBuddy(self.lineEdit_resolution) self.label_7.setBuddy(self.lineEdit_output) self.label_6.setBuddy(self.spinBox_max_number) self.label_5.setBuddy(self.spinBox_num_threads) - self.retranslateUi(MainWindow) self.checkBox_from_file.clicked['bool'].connect(self.lineEdit_keywords.setDisabled) self.checkBox_from_file.clicked['bool'].connect(self.pushButton_load_file.setEnabled) @@ -509,6 +542,7 @@ def setupUi(self, MainWindow): self.radioButton_google.toggled['bool'].connect(self.checkBox_safe_mode.setEnabled) self.radioButton_bing.toggled['bool'].connect(self.checkBox_safe_mode.setChecked) self.radioButton_baidu.toggled['bool'].connect(self.checkBox_safe_mode.setChecked) + self.radioButton_unsplash.toggled['bool'].connect(self.checkBox_safe_mode.setChecked) self.radioButton_google.toggled['bool'].connect(self.checkBox_safe_mode.setChecked) self.checkBox_from_file.clicked['bool'].connect(self.pushButton_load_file.click) self.radioButton_baidu.toggled['bool'].connect(self.widget_driver.setDisabled) @@ -531,7 +565,8 @@ def setupUi(self, MainWindow): MainWindow.setTabOrder(self.pushButton_cancel, self.radioButton_google) MainWindow.setTabOrder(self.radioButton_google, self.radioButton_bing) MainWindow.setTabOrder(self.radioButton_bing, self.radioButton_baidu) - MainWindow.setTabOrder(self.radioButton_baidu, self.plainTextEdit_log) + MainWindow.setTabOrder(self.radioButton_baidu, self.radioButton_unsplash) + MainWindow.setTabOrder(self.radioButton_unsplash, self.plainTextEdit_log) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate @@ -549,8 +584,10 @@ def retranslateUi(self, MainWindow): self.radioButton_google.setText(_translate("MainWindow", "&Google")) self.radioButton_bing.setText(_translate("MainWindow", "&Bing")) self.radioButton_baidu.setText(_translate("MainWindow", "B&aidu")) + self.radioButton_unsplash.setText(_translate("MainWindow", "Unsplash")) self.radioButton_chrome_headless.setText(_translate("MainWindow", "ChromeHeadless")) self.radioButton_chrome.setText(_translate("MainWindow", "Chrome")) + self.radioButton_firefox.setText(_translate("MainWindow", "FireFox")) self.radioButton_phantomjs.setText(_translate("MainWindow", "PhantomJs")) self.label.setText(_translate("MainWindow", "&Keywords:")) self.lineEdit_keywords.setToolTip(_translate("MainWindow", "Input keywords, seperated by comma \", \"")) @@ -560,6 +597,12 @@ def retranslateUi(self, MainWindow): self.lineEdit_path2file.setStatusTip(_translate("MainWindow", "Hint: Enter path to a text file. Each line of the file contains a group of keywords")) self.lineEdit_path2file.setPlaceholderText(_translate("MainWindow", "Path to file")) self.pushButton_load_file.setText(_translate("MainWindow", "...")) + + self.label_8.setText(_translate("MainWindow", "Resolution:")) + self.lineEdit_resolution.setToolTip(_translate("MainWindow", "HEIGHTxWIDTH e.g 1400x800")) + self.lineEdit_resolution.setStatusTip(_translate("MainWindow", "1400x800, 1920x1020 etc")) + self.lineEdit_resolution.setPlaceholderText(_translate("MainWindow", "HEIGHTxWIDTH E.g 1400x800, 1920x1020 etc.")) + self.label_7.setText(_translate("MainWindow", "&Output:")) self.lineEdit_output.setToolTip(_translate("MainWindow", "Path to output directory.")) self.lineEdit_output.setStatusTip(_translate("MainWindow", "Path to output directory.")) diff --git a/utils.py b/utils.py index ff8cab1c..74a3ee2b 100644 --- a/utils.py +++ b/utils.py @@ -19,6 +19,8 @@ def __init__(self): self.max_number = 0 + self.resolution = "" + self.face_only = False self.safe_mode = False @@ -57,6 +59,9 @@ def to_command_paras(self): str_paras += ' "' + self.keywords + '"' + if self.resolution != "": + str_paras += ' -es ' + self.resolution + return str_paras