import os import re import sys import configparser import time from pathlib import Path G_conf_override = { # index 0 save Config() first instance for quick access by using getInstance() 0: None, # register override config items "common:main_mode": None, "common:link_mode": None, "common:source_folder": None, "common:auto_exit": None, "common:nfo_skip_days": None, "common:stop_counter": None, "common:ignore_failed_list": None, "debug_mode:switch": None } def getInstance(): if isinstance(G_conf_override[0], Config): return G_conf_override[0] return Config() class Config: def __init__(self, path: str = "config.ini"): path_search_order = ( Path(path), Path.cwd() / "config.ini", Path.home() / "mdc.ini", Path.home() / ".mdc.ini", Path.home() / ".mdc/config.ini", Path.home() / ".config/mdc/config.ini" ) ini_path = None for p in path_search_order: if p.is_file(): ini_path = p.resolve() break if ini_path: self.conf = configparser.ConfigParser() self.ini_path = ini_path try: if self.conf.read(ini_path, encoding="utf-8-sig"): if G_conf_override[0] is None: G_conf_override[0] = self except UnicodeDecodeError: if self.conf.read(ini_path, encoding="utf-8"): if G_conf_override[0] is None: G_conf_override[0] = self except Exception as e: print("ERROR: Config file can not read!") print("读取配置文件出错!") print('=================================') print(e) print("======= Auto exit in 60s ======== ") time.sleep(60) os._exit(-1) else: print("ERROR: Config file not found!") print("Please put config file into one of the following path:") print('\n'.join([str(p.resolve()) for p in path_search_order[2:]])) # 对于找不到配置文件的情况,还是在打包时附上对应版本的默认配置文件,有需要时为其在搜索路径中生成, # 要比用户乱找一个版本不对应的配置文件会可靠些。这样一来,单个执行文件就是功能完整的了,放在任何 # 执行路径下都可以放心使用。 res_path = None # pyinstaller打包的在打包中找config.ini if hasattr(sys, '_MEIPASS') and (Path(getattr(sys, '_MEIPASS')) / 'config.ini').is_file(): res_path = Path(getattr(sys, '_MEIPASS')) / 'config.ini' # 脚本运行的所在位置找 elif (Path(__file__).resolve().parent / 'config.ini').is_file(): res_path = Path(__file__).resolve().parent / 'config.ini' if res_path is None: sys.exit(2) ins = input("Or, Do you want me create a config file for you? (Yes/No)[Y]:") if re.search('n', ins, re.I): sys.exit(2) # 用户目录才确定具有写权限,因此选择 ~/mdc.ini 作为配置文件生成路径,而不是有可能并没有写权限的 # 当前目录。目前版本也不再鼓励使用当前路径放置配置文件了,只是作为多配置文件的切换技巧保留。 write_path = path_search_order[2] # Path.home() / "mdc.ini" write_path.write_text(res_path.read_text(encoding='utf-8'), encoding='utf-8') print("Config file '{}' created.".format(write_path.resolve())) input("Press Enter key exit...") sys.exit(0) # self.conf = self._default_config() # try: # self.conf = configparser.ConfigParser() # try: # From single crawler debug use only # self.conf.read('../' + path, encoding="utf-8-sig") # except: # self.conf.read('../' + path, encoding="utf-8") # except Exception as e: # print("[-]Config file not found! Use the default settings") # print("[-]",e) # sys.exit(3) # #self.conf = self._default_config() def getboolean_override(self, section, item) -> bool: return self.conf.getboolean(section, item) if G_conf_override[f"{section}:{item}"] is None else bool( G_conf_override[f"{section}:{item}"]) def getint_override(self, section, item) -> int: return self.conf.getint(section, item) if G_conf_override[f"{section}:{item}"] is None else int( G_conf_override[f"{section}:{item}"]) def get_override(self, section, item) -> str: return self.conf.get(section, item) if G_conf_override[f"{section}:{item}"] is None else str( G_conf_override[f"{section}:{item}"]) def main_mode(self) -> int: try: return self.getint_override("common", "main_mode") except ValueError: self._exit("common:main_mode") def source_folder(self) -> str: return self.get_override("common", "source_folder") def failed_folder(self) -> str: return self.conf.get("common", "failed_output_folder") def success_folder(self) -> str: return self.conf.get("common", "success_output_folder") def actor_gender(self) -> str: return self.conf.get("common", "actor_gender") def link_mode(self) -> int: return self.getint_override("common", "link_mode") def scan_hardlink(self) -> bool: return self.conf.getboolean("common", "scan_hardlink", fallback=False)#未找到配置选项,默认不刮削 def failed_move(self) -> bool: return self.conf.getboolean("common", "failed_move") def auto_exit(self) -> bool: return self.getboolean_override("common", "auto_exit") def translate_to_sc(self) -> bool: return self.conf.getboolean("common", "translate_to_sc") def multi_threading(self) -> bool: return self.conf.getboolean("common", "multi_threading") def del_empty_folder(self) -> bool: return self.conf.getboolean("common", "del_empty_folder") def nfo_skip_days(self) -> int: try: return self.getint_override("common", "nfo_skip_days") except: return 30 def stop_counter(self) -> int: try: return self.getint_override("common", "stop_counter") except: return 0 def ignore_failed_list(self) -> bool: return self.getboolean_override("common", "ignore_failed_list") def download_only_missing_images(self) -> bool: return self.conf.getboolean("common", "download_only_missing_images") def mapping_table_validity(self) -> int: return self.conf.getint("common", "mapping_table_validity") def is_translate(self) -> bool: return self.conf.getboolean("translate", "switch") def is_trailer(self) -> bool: return self.conf.getboolean("trailer", "switch") def is_watermark(self) -> bool: return self.conf.getboolean("watermark", "switch") def is_extrafanart(self) -> bool: return self.conf.getboolean("extrafanart", "switch") def extrafanart_thread_pool_download(self) -> int: try: v = self.conf.getint("extrafanart", "parallel_download") return v if v >= 0 else 5 except: return 5 def watermark_type(self) -> int: return int(self.conf.get("watermark", "water")) def get_uncensored(self): try: sec = "uncensored" uncensored_prefix = self.conf.get(sec, "uncensored_prefix") # uncensored_poster = self.conf.get(sec, "uncensored_poster") return uncensored_prefix except ValueError: self._exit("uncensored") def get_extrafanart(self): try: extrafanart_download = self.conf.get("extrafanart", "extrafanart_folder") return extrafanart_download except ValueError: self._exit("extrafanart_folder") def get_translate_engine(self) -> str: return self.conf.get("translate", "engine") # def get_translate_appId(self) ->str: # return self.conf.get("translate","appid") def get_translate_key(self) -> str: return self.conf.get("translate", "key") def get_translate_delay(self) -> int: return self.conf.getint("translate", "delay") def translate_values(self) -> str: return self.conf.get("translate", "values") def get_translate_service_site(self) -> str: return self.conf.get("translate", "service_site") def proxy(self): try: sec = "proxy" switch = self.conf.get(sec, "switch") proxy = self.conf.get(sec, "proxy") timeout = self.conf.getint(sec, "timeout") retry = self.conf.getint(sec, "retry") proxytype = self.conf.get(sec, "type") iniProxy = IniProxy(switch, proxy, timeout, retry, proxytype) return iniProxy except ValueError: self._exit("common") def cacert_file(self) -> str: return self.conf.get('proxy', 'cacert_file') def media_type(self) -> str: return self.conf.get('media', 'media_type') def sub_rule(self): return self.conf.get('media', 'sub_type').split(',') def naming_rule(self) -> str: return self.conf.get("Name_Rule", "naming_rule") def location_rule(self) -> str: return self.conf.get("Name_Rule", "location_rule") def max_title_len(self) -> int: """ Maximum title length """ try: return self.conf.getint("Name_Rule", "max_title_len") except: return 50 def update_check(self) -> bool: try: return self.conf.getboolean("update", "update_check") except ValueError: self._exit("update:update_check") def sources(self) -> str: return self.conf.get("priority", "website") def escape_literals(self) -> str: return self.conf.get("escape", "literals") def escape_folder(self) -> str: return self.conf.get("escape", "folders") def debug(self) -> bool: return self.getboolean_override("debug_mode", "switch") def is_storyline(self) -> bool: try: return self.conf.getboolean("storyline", "switch") except: return True def storyline_site(self) -> str: try: return self.conf.get("storyline", "site") except: return "1:avno1,4:airavwiki" def storyline_censored_site(self) -> str: try: return self.conf.get("storyline", "censored_site") except: return "2:airav,5:xcity,6:amazon" def storyline_uncensored_site(self) -> str: try: return self.conf.get("storyline", "uncensored_site") except: return "3:58avgo" def storyline_show(self) -> int: try: v = self.conf.getint("storyline", "show_result") return v if v in (0, 1, 2) else 2 if v > 2 else 0 except: return 0 def storyline_mode(self) -> int: try: v = self.conf.getint("storyline", "run_mode") return v if v in (0, 1, 2) else 2 if v > 2 else 0 except: return 1 def cc_convert_mode(self) -> int: try: v = self.conf.getint("cc_convert", "mode") return v if v in (0, 1, 2) else 2 if v > 2 else 0 except: return 1 def cc_convert_vars(self) -> str: try: return self.conf.get("cc_convert", "vars") except: return "actor,director,label,outline,series,studio,tag,title" def javdb_sites(self) -> str: try: return self.conf.get("javdb", "sites") except: return "33,34" def face_locations_model(self) -> str: try: return self.conf.get("face", "locations_model") except: return "hog" @staticmethod def _exit(sec: str) -> None: print("[-] Read config error! Please check the {} section in config.ini", sec) input("[-] Press ENTER key to exit.") exit() @staticmethod def _default_config() -> configparser.ConfigParser: conf = configparser.ConfigParser() sec1 = "common" conf.add_section(sec1) conf.set(sec1, "main_mode", "1") conf.set(sec1, "source_folder", "./") conf.set(sec1, "failed_output_folder", "failed") conf.set(sec1, "success_output_folder", "JAV_output") conf.set(sec1, "link_mode", "0") conf.set(sec1, "scan_hardlink", "0") conf.set(sec1, "failed_move", "1") conf.set(sec1, "auto_exit", "0") conf.set(sec1, "translate_to_sc", "1") # actor_gender value: female or male or both or all(含人妖) conf.set(sec1, "actor_gender", "female") conf.set(sec1, "del_empty_folder", "1") conf.set(sec1, "nfo_skip_days", 30) conf.set(sec1, "stop_counter", 0) conf.set(sec1, "ignore_failed_list", 0) conf.set(sec1, "download_only_missing_images", 1) conf.set(sec1, "mapping_table_validity", 7) sec2 = "proxy" conf.add_section(sec2) conf.set(sec2, "proxy", "") conf.set(sec2, "timeout", "5") conf.set(sec2, "retry", "3") conf.set(sec2, "type", "socks5") conf.set(sec2, "cacert_file", "") sec3 = "Name_Rule" conf.add_section(sec3) conf.set(sec3, "location_rule", "actor + '/' + number") conf.set(sec3, "naming_rule", "number + '-' + title") conf.set(sec3, "max_title_len", "50") sec4 = "update" conf.add_section(sec4) conf.set(sec4, "update_check", "1") sec5 = "priority" conf.add_section(sec5) conf.set(sec5, "website", "airav,javbus,javdb,fanza,xcity,mgstage,fc2,fc2club,avsox,jav321,xcity") sec6 = "escape" conf.add_section(sec6) conf.set(sec6, "literals", "\()/") # noqa conf.set(sec6, "folders", "failed, JAV_output") sec7 = "debug_mode" conf.add_section(sec7) conf.set(sec7, "switch", "0") sec8 = "translate" conf.add_section(sec8) conf.set(sec8, "switch", "0") conf.set(sec8, "engine", "google-free") # conf.set(sec8, "appid", "") conf.set(sec8, "key", "") conf.set(sec8, "delay", "1") conf.set(sec8, "values", "title,outline") conf.set(sec8, "service_site", "translate.google.cn") sec9 = "trailer" conf.add_section(sec9) conf.set(sec9, "switch", "0") sec10 = "uncensored" conf.add_section(sec10) conf.set(sec10, "uncensored_prefix", "S2M,BT,LAF,SMD") sec11 = "media" conf.add_section(sec11) conf.set(sec11, "media_type", ".mp4,.avi,.rmvb,.wmv,.mov,.mkv,.flv,.ts,.webm,.MP4,.AVI,.RMVB,.WMV,.MOV,.MKV,.FLV,.TS,.WEBM,iso,ISO") conf.set(sec11, "sub_type", ".smi,.srt,.idx,.sub,.sup,.psb,.ssa,.ass,.txt,.usf,.xss,.ssf,.rt,.lrc,.sbv,.vtt,.ttml") sec12 = "watermark" conf.add_section(sec12) conf.set(sec12, "switch", 1) conf.set(sec12, "water", 2) sec13 = "extrafanart" conf.add_section(sec13) conf.set(sec13, "switch", 1) conf.set(sec13, "extrafanart_folder", "extrafanart") conf.set(sec13, "parallel_download", 1) sec14 = "storyline" conf.add_section(sec14) conf.set(sec14, "switch", 1) conf.set(sec14, "site", "1:avno1,4:airavwiki") conf.set(sec14, "censored_site", "2:airav,5:xcity,6:amazon") conf.set(sec14, "uncensored_site", "3:58avgo") conf.set(sec14, "show_result", 0) conf.set(sec14, "run_mode", 1) conf.set(sec14, "cc_convert", 1) sec15 = "cc_convert" conf.add_section(sec15) conf.set(sec15, "mode", 1) conf.set(sec15, "vars", "actor,director,label,outline,series,studio,tag,title") sec16 = "javdb" conf.add_section(sec16) conf.set(sec15, "sites", "33,34") return conf class IniProxy(): """ Proxy Config from .ini """ SUPPORT_PROXY_TYPE = ("http", "socks5", "socks5h") enable = False address = "" timeout = 5 retry = 3 proxytype = "socks5" def __init__(self, switch, address, timeout, retry, proxytype) -> None: """ Initial Proxy from .ini """ if switch == '1' or switch == 1: self.enable = True self.address = address self.timeout = timeout self.retry = retry self.proxytype = proxytype def proxies(self): ''' 获得代理参数,默认http代理 ''' if self.address: if self.proxytype in self.SUPPORT_PROXY_TYPE: proxies = {"http": self.proxytype + "://" + self.address, "https": self.proxytype + "://" + self.address} else: proxies = {"http": "http://" + self.address, "https": "https://" + self.address} else: proxies = {} return proxies if __name__ == "__main__": def evprint(evstr): code = compile(evstr, "", "eval") print('{}: "{}"'.format(evstr, eval(code))) config = Config() mfilter = {'conf', 'proxy', '_exit', '_default_config', 'getboolean_override', 'getint_override', 'get_override', 'ini_path'} for _m in [m for m in dir(config) if not m.startswith('__') and m not in mfilter]: evprint(f'config.{_m}()') pfilter = {'proxies', 'SUPPORT_PROXY_TYPE'} # test getInstance() assert (getInstance() == config) for _p in [p for p in dir(getInstance().proxy()) if not p.startswith('__') and p not in pfilter]: evprint(f'getInstance().proxy().{_p}') # Override Test G_conf_override["common:nfo_skip_days"] = 4321 G_conf_override["common:stop_counter"] = 1234 assert config.nfo_skip_days() == 4321 assert getInstance().stop_counter() == 1234 # remove override G_conf_override["common:stop_counter"] = None G_conf_override["common:nfo_skip_days"] = None assert config.nfo_skip_days() != 4321 assert config.stop_counter() != 1234 # Create new instance conf2 = Config() assert getInstance() != conf2 assert getInstance() == config G_conf_override["common:main_mode"] = 9 G_conf_override["common:source_folder"] = "A:/b/c" # Override effect to all instances assert config.main_mode() == 9 assert conf2.main_mode() == 9 assert getInstance().main_mode() == 9 assert conf2.source_folder() == "A:/b/c" print("### Override Test ###".center(36)) evprint('getInstance().main_mode()') evprint('config.source_folder()') G_conf_override["common:main_mode"] = None evprint('conf2.main_mode()') evprint('config.main_mode()') # unregister key acess will raise except try: print(G_conf_override["common:actor_gender"]) except KeyError as ke: print(f'Catched KeyError: {ke} is not a register key of G_conf_override dict.', file=sys.stderr) print(f"Load Config file '{conf2.ini_path}'.")