{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.10.12"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 这只是一个完整项目的一部分 不能运行的 \n- 可以从这个地址运行 [点击打开](https://www.kaggle.com/viyiviyi/sdwui-before)\n---","metadata":{"id":"6zE2QeUKLCtC"}},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"update_desc = '''\n欢迎使用 stable-diffusion-webui 便捷启动脚本\n\n此脚本理论上可以在任何jupyter环境运行,但仅在 google colab 和 kaggle 测试。\n此脚本的【前置脚本】发布地址 https://www.kaggle.com/code/yiyiooo/stable-diffusion-webui-novelai。\n此脚本需配合 【前置脚本】 的脚本附带的配置项才能正常启动。\n此脚本的内容会自动更新,你无需更新【前置脚本】就能获取到最新的功能。\n如果新功能需要增加新的配置项,可以在更新日志中查看到。\n\n路径说明\n* 为了解决平台差异,所有的安装目录和文件输出目录都被重新指定,如果你需要在配置中访问这些目录,请查看以下说明\n*\n* 如果链接了谷歌云盘,将会在云盘根目录创建 sdwebui 的文件夹,文件夹内的 Input 目录将会作为 输入目录; Output 将会作为输出目录\n* 可以使用 $install_path 或 {install_path} 来访问安装目录,写在字符串内也会生效\n* 项目跟目录固定是: {install_path}/sd_main_dir 或 $install_path/sd_main_dir\n* $output_path 或 {output_path} 可以访问输出目录\n* $input_path 或 {input_path} 可以访问输入目录 在kaggle是数据集根目录\n\n更新日志\n* 23-09-13\n* 修改了项目安装目录的文件夹名称,改为sd_main_dir,访问方式是{install_path}/sd_main_dir\n*\n* 23-08-02\n* 需要增加配置 [多实例 = True] 来使用第二张显卡\n*\n* 23-08-01\n* 估计是新的gradio的问题,需要禁用 --no-gradio-queue 启动参数才能正常访问,否则会一直处于加载界面,所以这个参数会被屏蔽不再生效\n*\n* 23-07-30\n* 使用新的文件加载逻辑重写完成,文件加载和加载更加简单,可以支持自定义下载或加载文件的目标目录了\n*\n* 23-07-26\n* 增加可隐藏启动时的不重要信息 通过增加配置 [hidden_console_info = True] 开启\n*\n* 23-07-22\n* 更新了可自动同步收藏目录到 https://huggingface.co/ 的功能\n* 可通过增加配置项 _huggingface_token = \"token\" 和 _huggingface_repo = “仓库id” 后使用此功能\n*\n* 23-07-18 \n* 如果有两个GPU 第二个GPU将会启动api服务,可用api的方式调用,和webui互相独立,绘图时不会导致另外一边卡顿\n* 通过/1/作为base url来访问这个api\n* 没有启动两个webui是因为,很大概率爆内存\n'''","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from pathlib import Path\nimport os\nimport time\nimport re\nimport subprocess\nimport threading\nimport sys\nimport socket\nimport torch\nfrom typing import List","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 内置参数默认值,当上下文有参数时可覆盖默认值\n\n_useFrpc = False\nif 'useFrpc' in locals() or 'useFrpc' in globals():\n _useFrpc = useFrpc\n \n_useNgrok = False\nif 'useNgrok' in locals() or 'useNgrok' in globals():\n _useNgrok = useNgrok\n \n_reLoad = False\nif 'reLoad' in locals() or 'reLoad' in globals():\n _reLoad = reLoad\n \n_普通文件列表 = ''\nif '普通文件列表' in locals() or '普通文件列表' in globals():\n _普通文件列表 = 普通文件列表\n\n_重要文件列表 = ''\nif '重要文件列表' in locals() or '重要文件列表' in globals():\n _重要文件列表 = 重要文件列表\n\n_按顺序加载的重要文件列表 = ''\nif '按顺序加载的重要文件列表' in locals() or '按顺序加载的重要文件列表' in globals():\n _按顺序加载的重要文件列表 = 按顺序加载的重要文件列表\n \n_webuiPort = 7860\nif 'webuiPort' in locals() or 'webuiPort' in globals():\n _webuiPort = webuiPort\n \n_webui_git_repo ='https://github.com/viyiviyi/stable-diffusion-webui.git -b local' \nif 'webui_git_repo' in locals() or 'webui_git_repo' in globals():\n _webui_git_repo = webui_git_repo\n \n_webui_config_git_repu = 'https://github.com/viyiviyi/sd-configs.git'\nif 'webui_config_git_repu' in locals() or 'webui_config_git_repu' in globals():\n _webui_config_git_repu = webui_config_git_repu\n \n_huggingface_token = '{input_path}/configs/huggingface_token.txt'\nif 'huggingface_token' in locals() or 'huggingface_token' in globals():\n _huggingface_token = huggingface_token\n \n_huggingface_repo = ''\nif 'webui_config_git_repu' in locals() or 'webui_config_git_repu' in globals():\n _webui_config_git_repu = webui_config_git_repu\n\n_link_instead_of_copy = True\nif 'link_instead_of_copy' in locals() or 'link_instead_of_copy' in globals():\n _link_instead_of_copy = link_instead_of_copy\n \nshow_shell_info = False\nif 'hidden_console_info' in locals() or 'hidden_console_info' in globals():\n show_shell_info = not hidden_console_info\n\nrun_by_none_device = False","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\ndef run(command, cwd=None, desc=None, errdesc=None, custom_env=None,try_error:bool=True) -> str:\n global show_shell_info\n if desc is not None:\n print(desc)\n\n run_kwargs = {\n \"args\": command,\n \"shell\": True,\n \"cwd\": cwd,\n \"env\": os.environ if custom_env is None else custom_env,\n \"encoding\": 'utf8',\n \"errors\": 'ignore',\n }\n\n if not show_shell_info:\n run_kwargs[\"stdout\"] = run_kwargs[\"stderr\"] = subprocess.PIPE\n\n result = subprocess.run(**run_kwargs)\n\n if result.returncode != 0:\n error_bits = [\n f\"{errdesc or 'Error running command'}.\",\n f\"Command: {command}\",\n f\"Error code: {result.returncode}\",\n ]\n if result.stdout:\n error_bits.append(f\"stdout: {result.stdout}\")\n if result.stderr:\n error_bits.append(f\"stderr: {result.stderr}\")\n if try_error:\n print(RuntimeError(\"\\n\".join(error_bits)))\n else:\n raise RuntimeError(\"\\n\".join(error_bits))\n\n if show_shell_info:\n print(result.stdout or \"\")\n return (result.stdout or \"\")\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\n# 检查gpu是否存在\ndef check_gpu():\n if not run_by_none_device and torch.cuda.device_count() == 0:\n raise Exception('当前环境没有GPU')\n\ninstall_path=f\"{os.environ['HOME']}/sdwui\" # 安装目录\noutput_path=f\"{os.environ['HOME']}/.sdwui/Output\" # 输出目录 如果使用google云盘 会在google云盘增加sdwebui/Output\ninput_path = '/kaggle/input' # 输入目录\nui_dir_name = 'sd'\ngoogle_drive = '' \n\n_useGooglrDrive = True\nif 'useGooglrDrive' in locals() or 'useGooglrDrive' in globals():\n _useGooglrDrive = useGooglrDrive\n\n_按顺序加载的重要文件列表 = ''\nif '按顺序加载的重要文件列表' in locals() or '按顺序加载的重要文件列表' in globals():\n _按顺序加载的重要文件列表 = 按顺序加载的重要文件列表\n# 连接谷歌云\ntry:\n if _useGooglrDrive:\n from google.colab import drive\n drive.mount(f'~/google_drive')\n google_drive = f\"{os.environ['HOME']}/google_drive/MyDrive\"\n output_path = f'{google_drive}/sdwebui/Output'\n input_path = f'{google_drive}/sdwebui/Input'\n run(f'''mkdir -p {input_path}''')\n print('''\n已经链接到谷歌云盘\n云盘根目录/sdwebui/Output 被链接为输出目录,图片和设置文件将会保存在此文件夹\n云盘根目录/sdwebui/Input 被链接为输入目录,环境变量名是 $input_path, 可以在任何一个配置内通过$input_path或{input_path}来读取文件\n ''')\nexcept:\n _useGooglrDrive = False\n\nrun(f'''mkdir -p {install_path}''')\nrun(f'''mkdir -p {output_path}''')\n\nos.environ['install_path'] = install_path\nos.environ['output_path'] = output_path\nos.environ['google_drive'] = google_drive\nos.environ['input_path'] = input_path\n\ndef replace_path(input_str:str):\n return input_str.replace('$install_path',install_path)\\\n .replace('{install_path}',install_path)\\\n .replace('$input_path',input_path)\\\n .replace('{input_path}',input_path)\\\n .replace('$output_path',output_path)\\\n .replace('{output_path}',output_path)\n\nspace_string = ' \\n\\r\\t\\'\\\",'\n\ndef config_reader(conf:str):\n args = [replace_path(item.split('#')[0].strip(space_string)) for item in conf.split('\\n') if item.strip(space_string)]\n return [item.strip() for item in args if item.strip()]\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ngrokTokenFile = os.path.join(input_path,'configs/ngrok_token.txt') # 非必填 存放ngrokToken的文件的路径\nfrpcConfigFile = os.path.join(input_path,'configs/frpc_koishi.ini') # 非必填 frp 配置文件\n# ss证书目录 下载nginx的版本,把pem格式改成crt格式\nfrpcSSLFFlies =[os.path.join(input_path,'configs/koishi_ssl')]\nif 'frpSSL文件' in locals() or 'frpSSL文件' in globals():\n frpcSSLFFlies = frpcSSLFFlies + config_reader(frpSSL文件)\n# frpc 文件目录 如果目录不存在,会自动下载,也可以在数据集搜索 viyiviyi/utils 添加\nfrpcExePath = os.path.join(input_path,'utils-tools/frpc')\n# 其他需要加载的webui启动参数 写到【参数列表】这个配置去\notherArgs = '--xformers'\nif '参数列表' in locals() or '参数列表' in globals():\n otherArgs = ' '.join([item for item in config_reader(参数列表) if item != '--no-gradio-queue'])\nvenvPath = os.path.join(input_path,'sd-webui-venv/venv.tar.bak') # 安装好的python环境 sd-webui-venv是一个公开是数据集 可以搜索添加\n\n# 用于使用kaggle api的token文件 参考 https://www.kaggle.com/docs/api\n# 此文件用于自动上传koishi的相关配置 也可以用于保存重要的输出文件\nkaggleApiTokenFile = os.path.join(input_path,'configs/kaggle.json')\n\nrequirements = []\n","metadata":{"_kg_hide-input":true,"id":"i3LhnwYHLCtC","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 这下面的是用于初始化一些值或者环境变量的,轻易别改\n_setting_file = '/kaggle/working/configs/config.json'\nif 'setting_file' in locals() or 'setting_file' in globals():\n _setting_file = replace_path(_setting_file)\n_ui_config_file = '/kaggle/working/configs/ui-config.json'\nif 'ui_config_file' in locals() or 'ui_config_file' in globals():\n _ui_config_file = replace_path(ui_config_file)\n\n# 设置文件路径\nif Path(f\"{os.environ['HOME']}/google_drive/MyDrive\").exists():\n if _setting_file == '/kaggle/working/configs/config.json':\n _setting_file = os.path.join(output_path,'configs/config.json')\n if _ui_config_file == '/kaggle/working/configs/ui-config.json':\n _ui_config_file = os.path.join(output_path,'configs/ui-config.json')\n \nfrpcStartArg = ''\nrun(f'''mkdir -p {install_path}/configFiles''')\nif 'frp配置文件或配置' in locals() or 'frp配置文件或配置' in globals():\n _frp配置文件或配置 = replace_path(frp配置文件或配置)\n if Path(_frp配置文件或配置.strip()).exists():\n frpcConfigFile = _frp配置文件或配置.strip()\n if not Path(frpcConfigFile).exists(): \n if _frp配置文件或配置.strip().startswith('-f'):\n frpcStartArg = _frp配置文件或配置.strip()\n else:\n print('没有frpcp配置')\n _useFrpc = False\n else:\n run(f'''cp -f {frpcConfigFile} {install_path}/configFiles/frpc_webui.ini''')\n frpcConfigFile = f'{install_path}/configFiles/frpc_webui.ini'\n run(f'''sed -i \"s/local_port = .*/local_port = {_webuiPort}/g\" {frpcConfigFile}''')\n frpcStartArg = f' -c {frpcConfigFile}'\n\nngrokToken=''\nif 'ngrok配置或文件地址' in locals() or 'ngrok配置或文件地址' in globals():\n _ngrok配置或文件地址 = replace_path(ngrok配置或文件地址)\n if Path(_ngrok配置或文件地址.strip()).exists():\n ngrokTokenFile = _ngrok配置或文件地址.strip()\n if Path(ngrokTokenFile).exists():\n with open(ngrokTokenFile,encoding = \"utf-8\") as nkfile:\n ngrokToken = nkfile.readline()\n elif not _ngrok配置或文件地址.strip().startswith('/'):\n ngrokToken=_ngrok配置或文件地址.strip()\n \nif not Path(venvPath).exists():\n venvPath = os.path.join(input_path,'sd-webui-venv/venv.zip')\n","metadata":{"_kg_hide-input":true,"id":"a_GtG2ayLCtD","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 文件下载工具\n\n---\n\nlink_or_download_flie(config:str, skip_url:bool=False, _link_instead_of_copy:bool=True, base_path:str = '',sync:bool=False,thread_num:int=None)","metadata":{}},{"cell_type":"code","source":"import concurrent.futures\nimport importlib\nimport os\nimport pprint\nimport re\nfrom pathlib import Path\nfrom typing import List\n\nimport requests\n\nshow_shell_info = False\n\ndef is_installed(package):\n try:\n spec = importlib.util.find_spec(package)\n except ModuleNotFoundError:\n return False\n\n return spec is not None\n\ndef download_file(url:str, filename:str, dist_path:str, cache_path = '',_link_instead_of_copy:bool=True):\n # 获取文件的真实文件名\n if not filename:\n with requests.get(url, stream=True) as r:\n if 'Content-Disposition' in r.headers:\n filename = r.headers['Content-Disposition'].split('filename=')[1].strip('\"')\n r.close()\n if not filename and re.search(r'/[^/]+\\.[^/]+$',url):\n filename = url.split('/')[-1]\n \n filename = re.sub(r'[\\\\/:*?\"<>|;]', '', filename)\n filename = re.sub(r'[\\s\\t]+', '_', filename)\n \n if show_shell_info:\n print(f'下载 {filename} url: {url} --> {dist_path}')\n \n # 创建目录\n if cache_path and not Path(cache_path).exists():\n os.makedirs(cache_path,exist_ok=True)\n if dist_path and not Path(dist_path).exists():\n os.makedirs(dist_path,exist_ok=True)\n \n # 拼接文件的完整路径\n filepath = os.path.join(dist_path, filename)\n\n if cache_path:\n cache_path = os.path.join(cache_path, filename)\n \n # 判断文件是否已存在\n if Path(filepath).exists():\n print(f'文件 {filename} 已存在 {dist_path}')\n return\n \n if cache_path and Path(cache_path).exists():\n run(f'cp -n -r -f {\"-s\" if _link_instead_of_copy else \"\"} {cache_path} {dist_path}')\n if show_shell_info:\n print(f'文件缓存 {cache_path} --> {dist_path}')\n return\n # 下载文件\n with requests.get(url, stream=True) as r:\n r.raise_for_status()\n with open(cache_path or filepath, 'wb') as f:\n for chunk in r.iter_content(chunk_size=8192):\n if chunk:\n f.write(chunk)\n # 如果使用了缓存目录 需要复制或链接文件到目标目录\n if cache_path:\n run(f'cp -n -r -f {\"-s\" if _link_instead_of_copy else \"\"} {cache_path} {dist_path}')\n if show_shell_info:\n print(f'下载完成 {filename} --> {dist_path}')\n \ndef download_git(url, dist_path, cache_path = '',_link_instead_of_copy:bool=True):\n if not Path(dist_path).exists():\n os.makedirs(dist_path,exist_ok=True)\n if show_shell_info:\n print(f'git 下载 {url} --> {dist_path}')\n if cache_path and not Path(cache_path).exists():\n os.makedirs(cache_path,exist_ok=True)\n run(f'git clone {url}',cwd = cache_path)\n if cache_path:\n run(f'cp -n -r -f {cache_path}/* {dist_path}')\n else:\n run(f'git clone {url}',cwd = dist_path)\n if show_shell_info:\n print(f'git 下载完成 {url} --> {dist_path}')\n \n \n \n# 加入文件到下载列表\ndef pause_url(url:str,dist_path:str):\n file_name = ''\n if re.match(r'^[^:]+:(https?|ftps?)://', url, flags=0):\n file_name = re.findall(r'^[^:]+:',url)[0][:-1]\n url = url[len(file_name)+1:]\n if not re.match(r'^(https?|ftps?)://',url):\n return\n file_name = re.sub(r'\\s+','_',file_name or '')\n path_hash = str(hash(url)).replace('-','')\n \n return {'file_name':file_name,'path_hash':path_hash,'url':url,'dist_path':dist_path}\n\ndef download_urls(download_list:List[dict],sync:bool=False,thread_num:int=5, \n cache_path:str=os.path.join(os.environ['HOME'],'.cache','download_util'),\n _link_instead_of_copy:bool=True,is_await:bool=False):\n if sync:\n for conf in download_list:\n cache_dir = os.path.join(cache_path,conf['path_hash'])\n if conf['url'].startswith('https://github.com'):\n download_git(conf['url'],conf['dist_path'],cache_path=cache_dir,_link_instead_of_copy=_link_instead_of_copy)\n continue\n download_file(conf['url'],conf['file_name'],conf['dist_path'],cache_path=cache_dir,_link_instead_of_copy=_link_instead_of_copy)\n else:\n executor = concurrent.futures.ThreadPoolExecutor(max_workers=thread_num)\n futures = []\n for conf in download_list:\n cache_dir = os.path.join(cache_path,conf['path_hash'])\n if conf['url'].startswith('https://github.com'):\n futures.append(executor.submit(download_git, conf['url'],conf['dist_path'],\n cache_path=cache_dir,_link_instead_of_copy=_link_instead_of_copy))\n continue\n futures.append(executor.submit(download_file, conf['url'],conf['file_name'],conf['dist_path'],\n cache_path=cache_dir,_link_instead_of_copy=_link_instead_of_copy))\n if is_await:\n concurrent.futures.wait(futures)\n \n \ndef parse_config(config:str):\n space_string = ' \\n\\r\\t\\'\\\",'\n other_flie_list = [item.split('#')[0].strip(space_string) for item in config.split('\\n') if item.strip(space_string)]\n other_flie_list = [item.strip() for item in other_flie_list if item.strip()]\n other_flie_list_store = {}\n other_flie_list_store_name='default'\n other_flie_list_store_list_cache=[]\n \n for item in other_flie_list:\n if item.startswith('[') and item.endswith(']'):\n if not other_flie_list_store_name == 'default':\n other_flie_list_store[other_flie_list_store_name]=other_flie_list_store_list_cache\n other_flie_list_store_list_cache = []\n other_flie_list_store_name = item[1:-1]\n else:\n other_flie_list_store_list_cache.append(item)\n other_flie_list_store[other_flie_list_store_name]=other_flie_list_store_list_cache\n \n return other_flie_list_store\n\n\ndef link_or_download_flie(config:str, skip_url:bool=False, _link_instead_of_copy:bool=True, base_path:str = '',\n sync:bool=False,thread_num:int=None, is_await:bool=False):\n store:dict[str,List[str]] = parse_config(config)\n download_list = []\n for dist_dir in store.keys():\n dist_path = os.path.join(base_path,dist_dir)\n os.makedirs(dist_path,exist_ok=True)\n for path in store[dist_dir]:\n if 'https://' in path or 'http://' in path:\n if skip_url:\n continue\n if sync:\n download_urls([pause_url(path,dist_path)],_link_instead_of_copy = _link_instead_of_copy, sync=sync)\n continue\n download_list.append(pause_url(path,dist_path))\n else:\n run(f'cp -n -r -f {\"-s\" if _link_instead_of_copy else \"\"} {path} {dist_path}')\n if show_shell_info:\n print(f'{\"链接\" if _link_instead_of_copy else \"复制\"} {path} --> {dist_path}')\n run(f'rm -f {dist_path}/\\*.* ')\n if not skip_url:\n if show_shell_info:\n pprint.pprint(download_list)\n download_urls(download_list,_link_instead_of_copy = _link_instead_of_copy, sync=sync, thread_num=thread_num or 2,is_await=is_await)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## kaggle public API\n\n**不能使用%cd这种会改变当前工作目录的命令,会导致和其他线程冲突**\n\n---","metadata":{"id":"p0uS-BLULCtD"}},{"cell_type":"code","source":"# 安装kaggle的api token文件\ndef initKaggleConfig():\n if Path('~/.kaggle/kaggle.json').exists():\n return True\n if Path(kaggleApiTokenFile).exists():\n run(f'''mkdir -p ~/.kaggle/''')\n run('cp '+kaggleApiTokenFile+' ~/.kaggle/kaggle.json')\n run(f'''chmod 600 ~/.kaggle/kaggle.json''')\n return True\n print('缺少kaggle的apiToken文件,访问:https://www.kaggle.com/你的kaggle用户名/account 获取')\n return False\n\ndef getUserName():\n if not initKaggleConfig(): return\n import kaggle\n return kaggle.KaggleApi().read_config_file()['username']\n\ndef createOrUpdateDataSet(path:str,datasetName:str):\n if not initKaggleConfig(): return\n print('创建或更新数据集 '+datasetName)\n import kaggle\n run(f'mkdir -p {install_path}/kaggle_cache')\n run(f'rm -rf {install_path}/kaggle_cache/*')\n datasetDirPath = install_path+'/kaggle_cache/'+datasetName\n run('mkdir -p '+datasetDirPath)\n run('cp -f '+path+' '+datasetDirPath+'/')\n username = getUserName()\n print(\"kaggle username:\"+username)\n datasetPath = username+'/'+datasetName\n datasetList = kaggle.api.dataset_list(mine=True,search=datasetPath)\n print(datasetList)\n if len(datasetList) == 0 or datasetPath not in [str(d) for d in datasetList]: # 创建 create\n run('kaggle datasets init -p' + datasetDirPath)\n metadataFile = datasetDirPath+'/dataset-metadata.json'\n run('sed -i s/INSERT_TITLE_HERE/'+ datasetName + '/g ' + metadataFile)\n run('sed -i s/INSERT_SLUG_HERE/'+ datasetName + '/g ' + metadataFile)\n run('cat '+metadataFile)\n run('kaggle datasets create -p '+datasetDirPath)\n print('create database done')\n else:\n kaggle.api.dataset_metadata(datasetPath,datasetDirPath)\n kaggle.api.dataset_create_version(datasetDirPath, 'auto update',dir_mode='zip')\n print('upload database done')\n\ndef downloadDatasetFiles(datasetName:str,outputPath:str):\n if not initKaggleConfig(): return\n print('下载数据集文件 '+datasetName)\n import kaggle\n username = getUserName()\n datasetPath = username+'/'+datasetName\n datasetList = kaggle.api.dataset_list(mine=True,search=datasetPath)\n if datasetPath not in [str(d) for d in datasetList]:\n return False\n run('mkdir -p '+outputPath)\n kaggle.api.dataset_download_files(datasetPath,path=outputPath,unzip=True)\n return True\n\n","metadata":{"_kg_hide-input":true,"id":"m8FJi4j0LCtD","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 同步文件夹到 huggingface\n\n---","metadata":{}},{"cell_type":"code","source":"# 文件夹与 huggingface 同步\nif '_huggingface_token' in locals() and '_huggingface_repo' in locals():\n if not is_installed('watchdog'):\n requirements.append('watchdog')\n if not is_installed('huggingface_hub'):\n requirements.append('huggingface_hub')\n else:\n try:\n from huggingface_hub import HfApi,login,snapshot_download\n except:\n requirements.append('huggingface_hub')\n\nhuggingface_is_init = False\n\ndef init_huggingface():\n if '_huggingface_token' not in globals() or '_huggingface_repo' not in globals():\n print('请增加配置项 _huggingface_token = \"token\" 和 _huggingface_repo = “仓库id” 后使用 huggingface 功能')\n return False\n if not _huggingface_repo or not _huggingface_token:\n print('当前无huggingface配置,可配置huggingface仓库和token将收藏的图片保存到huggingface仓库')\n return False\n global huggingface_is_init\n from huggingface_hub import HfApi,login,snapshot_download\n token = replace_path(_huggingface_token)\n if Path(token).exists():\n with open(token,encoding = \"utf-8\") as nkfile:\n token = nkfile.readline()\n if not token.startswith('hf_'):\n print('huggingface token 不正确,请将 token 或 仅存放token 的txt文件路径填入 _huggingface_token 配置')\n return False\n login(token,add_to_git_credential=True)\n huggingface_is_init = True\n return True\n\n\ndef download__huggingface_repo(repo_id:str,dist_directory:str=None,repo_type='datasets',callback=None):\n if not huggingface_is_init:\n print('huggingface 相关功能未初始化 请调用 init_huggingface() 初始化')\n \n if not dist_directory:\n dist_directory = f'{install_path}/sd_main_dir/log'\n \n from huggingface_hub import HfApi,login,snapshot_download\n \n api = HfApi()\n \n print('下载收藏的图片')\n run(f'''mkdir -p {install_path}/cache/huggingface''')\n if not Path(f'{install_path}/cache/huggingface/{repo_id.split(\"/\")[1]}').exists():\n run(f'git clone https://huggingface.co/{repo_type}/{repo_id}',cwd=f'{install_path}/cache/huggingface')\n run(f'cp -r -f -n -s {install_path}/cache/huggingface/{repo_id.split(\"/\")[1]}/* {dist_directory}')\n# snapshot_download(repo_id = repo_id, local_dir = dist_directory, local_dir_use_symlinks = \"auto\", token=True, repo_type=repo_type )\n if callback:\n callback()\n\ndef start_sync_log_to_huggingface(repo_id:str,directory_to_watch:str=None,repo_type='datasets'):\n if not huggingface_is_init:\n print('huggingface 相关功能未初始化 请调用 init_huggingface() 初始化')\n \n from watchdog.observers import Observer\n from watchdog.events import FileSystemEventHandler\n from huggingface_hub import HfApi,login,snapshot_download\n \n # 配置监视的目录和 Hugging Face 仓库信息\n class FileChangeHandler(FileSystemEventHandler):\n def __init__(self, api, repo_id, repo_type):\n self.api = api\n self.repo_id = repo_id\n self.repo_type = repo_type\n def on_created(self, event):\n if not event.is_directory:\n # 上传新文件到 Hugging Face 仓库\n file_path = event.src_path\n file_name = os.path.basename(file_path)\n if file_name[-4:] not in ['.png','.jpg','.txt']: return\n try:\n self.api.upload_file(\n path_or_fileobj=file_path,\n path_in_repo=file_path.replace(directory_to_watch,''),\n repo_id=self.repo_id,\n repo_type=self.repo_type,\n )\n except Error as error:\n print(error)\n\n def on_deleted(self, event):\n if not event.is_directory:\n # 从 Hugging Face 仓库删除文件\n file_path = event.src_path\n file_name = os.path.basename(file_path)\n if file_name[-4:] not in ['.png','.jpg','.txt']: return\n try:\n self.api.delete_file(\n path_in_repo=file_path.replace(directory_to_watch,''),\n repo_id=self.repo_id,\n repo_type=self.repo_type,\n )\n except Error as error:\n print(error)\n\n def on_modified(self, event):\n if not event.is_directory:\n # 更新 Hugging Face 仓库中的文件\n file_path = event.src_path\n file_name = os.path.basename(file_path)\n if file_name[-4:] not in ['.png','.jpg','.txt']: return\n try:\n self.api.upload_file(\n path_or_fileobj=file_path,\n path_in_repo=file_path.replace(directory_to_watch,''),\n repo_id=self.repo_id,\n repo_type=self.repo_type,\n )\n except Error as error:\n print(error)\n\n def on_moved(self, event):\n if not event.is_directory:\n file_path = event.dest_path\n file_name = os.path.basename(file_path)\n if file_name[-4:] not in ['.png','.jpg','.txt']: return\n if event.dest_path.startswith(directory_to_watch):\n try:\n self.api.upload_file(\n path_or_fileobj=file_path,\n path_in_repo=file_path.replace(directory_to_watch,''),\n repo_id=self.repo_id,\n repo_type=self.repo_type,\n )\n except Error as error:\n print(error)\n\n api = HfApi()\n \n if not directory_to_watch:\n directory_to_watch = f'{install_path}/sd_main_dir/log'\n # 创建观察者对象并注册文件变化处理程序\n event_handler = FileChangeHandler(api,repo_id,repo_type)\n observer = Observer()\n observer.schedule(event_handler, directory_to_watch, recursive=True)\n\n # 启动观察者\n observer.name = \"solo_directory_to_watch\"\n print(f'启动收藏图片文件夹监听,并自动同步到 huggingface {repo_type} : {repo_id}')\n observer.start()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 工具函数\n**不能使用%cd这种会改变当前工作目录的命令,会导致和其他线程冲突**\n\n---","metadata":{"id":"sswa04veLCtE"}},{"cell_type":"code","source":"def echoToFile(content:str,path:str):\n if path.find('/') >= 0:\n _path = '/'.join(path.split('/')[:-1])\n run(f'''mkdir -p {_path}''')\n with open(path,'w') as sh:\n sh.write(content)\n\ndef zipPath(path:str,zipName:str,format='tar'):\n if path.startswith('$install_path'):\n path = path.replace('$install_path',install_path)\n if path.startswith('$output_path'):\n path = path.replace('$install_path',output_path)\n if not path.startswith('/'):\n path = f'{install_path}/sd_main_dir/{path}'\n if Path(path).exists():\n if 'tar' == format:\n run(f'tar -cf {output_path}/'+ zipName +'.tar -C '+ path +' . ')\n elif 'gz' == format:\n run(f'tar -czf {output_path}/'+ zipName +'.tar.gz -C '+ path +' . ')\n return\n print('指定的目录不存在:'+path)\n\n# 检查网络\ndef check_service(host, port):\n try:\n socket.create_connection((host, port), timeout=5)\n return True\n except socket.error:\n return False","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 内网穿透\n\n---","metadata":{}},{"cell_type":"code","source":"# ngrok\ndef startNgrok(ngrokToken:str,ngrokLocalPort:int):\n if not is_installed('pyngrok'):\n run(f'pip install pyngrok')\n from pyngrok import conf, ngrok\n try:\n conf.get_default().auth_token = ngrokToken\n conf.get_default().monitor_thread = False\n ssh_tunnels = ngrok.get_tunnels(conf.get_default())\n if len(ssh_tunnels) == 0:\n ssh_tunnel = ngrok.connect(ngrokLocalPort)\n print('ngrok 访问地址:'+ssh_tunnel.public_url)\n else:\n print('ngrok 访问地址:'+ssh_tunnels[0].public_url)\n except:\n print('启动ngrok出错')\n \ndef startFrpc(name,configFile):\n echoToFile(f'''\ncd {install_path}/frpc/\n{install_path}/frpc/frpc {configFile}\n''',f'{install_path}/frpc/start.sh')\n run(f'''bash {install_path}/frpc/start.sh''')\n \ndef installProxyExe():\n if _useFrpc:\n print('安装frpc')\n run(f'mkdir -p {install_path}/frpc')\n if Path(frpcExePath).exists():\n run(f'cp -f -n {frpcExePath} {install_path}/frpc/frpc')\n else:\n run(f'wget \"https://huggingface.co/datasets/ACCA225/Frp/resolve/main/frpc\" -O {install_path}/frpc/frpc')\n \n for ssl in frpcSSLFFlies:\n if Path(ssl).exists():\n run(f'cp -f -n {ssl}/* {install_path}/frpc/')\n run(f'chmod +x {install_path}/frpc/frpc')\n run(f'{install_path}/frpc/frpc -v')\n if _useNgrok and not is_installed('pyngrok'):\n run('pip install pyngrok')\n\ndef startProxy():\n if _useNgrok:\n startNgrok(ngrokToken,_webuiPort)\n if _useFrpc:\n startFrpc('frpc_proxy',frpcStartArg)","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"id":"coqQvTSLLCtE","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## NGINX 反向代理\n\n---","metadata":{}},{"cell_type":"code","source":"\n# nginx 反向代理配置文件\ndef localProxy():\n conf = '''\nserver\n{\n listen '''+str(_webuiPort)+''';\n listen [::]:'''+str(_webuiPort)+''';\n server_name 127.0.0.1 localhost 0.0.0.0 \"\";\n \n if ($request_method = OPTIONS) {\n return 200;\n }\n fastcgi_send_timeout 10m;\n fastcgi_read_timeout 10m;\n fastcgi_connect_timeout 10m;\n location /1/\n {\n proxy_pass http://127.0.0.1:'''+str(_webuiPort+2)+'''/;\n # add_header Set-Cookie \"subpath=1; expires=0; Path=/\";\n # proxy_set_header Set-Cookie \"subpath=1; expires=0; Path=/\";\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header REMOTE-HOST $remote_addr;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection upgrade;\n proxy_http_version 1.1;\n proxy_connect_timeout 10m;\n proxy_read_timeout 10m;\n }\n location /\n {\n set $proxy_url http://127.0.0.1:'''+str(_webuiPort+1)+''';\n # if ($cookie_subpath = \"1\") {\n # set $proxy_url http://127.0.0.1:'''+str(_webuiPort+2)+''';\n # }\n proxy_pass $proxy_url;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header REMOTE-HOST $remote_addr;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection upgrade;\n proxy_http_version 1.1;\n proxy_connect_timeout 10m;\n proxy_read_timeout 10m;\n }\n}\n'''\n echoToFile(conf,'/etc/nginx/conf.d/proxy_nginx.conf')\n if not check_service('localhost',_webuiPort):\n run(f'''nginx -c /etc/nginx/nginx.conf''')\n run(f'''nginx -s reload''')\n ","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 线程清理工具\n\n---\n\n清理线程名以 solo_ 开头的所有线程","metadata":{}},{"cell_type":"code","source":"import inspect\nimport ctypes\n\ndef _async_raise(tid, exctype):\n \"\"\"raises the exception, performs cleanup if needed\"\"\"\n tid = ctypes.c_long(tid)\n if not inspect.isclass(exctype):\n exctype = type(exctype)\n res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))\n if res == 0:\n raise ValueError(\"invalid thread id\")\n elif res != 1:\n # \"\"\"if it returns a number greater than one, you're in trouble,\n # and you should call it again with exc=NULL to revert the effect\"\"\"\n ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)\n raise SystemError(\"PyThreadState_SetAsyncExc failed\")\n\ndef stop_thread(thread):\n _async_raise(thread.ident, SystemExit)\n\ndef stop_solo_threads():\n # 获取当前所有活动的线程\n threads = threading.enumerate()\n # 关闭之前创建的子线程\n for thread in threads:\n if thread.name.startswith('solo_'):\n print(f'结束线程:{thread.name}')\n try:\n stop_thread(thread)\n except socket.error:\n print(f'结束线程:{thread.name} 执行失败')","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# webui 安装和配置函数\n---","metadata":{"id":"Ve3p8oOkLCtE"}},{"cell_type":"code","source":"envInstalled=False\nquickStart = True\n#安装webui\ndef install():\n print('安装webui')\n os.chdir(f'''{install_path}''')\n run(f'''git lfs install''')\n run(f'''git config --global credential.helper store''')\n for requirement in requirements:\n run(f'pip install {requirement}')\n if _reLoad:\n run(f'''rm -rf sd_main_dir''')\n if Path(\"sd_main_dir\").exists():\n os.chdir(f'''{install_path}/sd_main_dir/''')\n run(f'''git checkout .''')\n run(f'''git pull''')\n else:\n run(f'''git clone {_webui_git_repo} sd_main_dir''')\n os.chdir(f'''{install_path}/sd_main_dir''')\n print('安装webui 完成')\n\n# 链接输出目录\ndef link_dir():\n print('链接输出目录')\n # 链接图片输出目录\n run(f'''mkdir -p {output_path}/outputs''')\n run(f'''rm -rf {install_path}/sd_main_dir/outputs''')\n run(f'''ln -s -r {output_path}/outputs {install_path}/sd_main_dir/''')\n # 输出收藏目录\n run(f'''mkdir -p {output_path}/log''')\n run(f'''rm -rf {install_path}/sd_main_dir/log''')\n run(f'''ln -s -r {output_path}/log {install_path}/sd_main_dir/''')\n # 链接训练输出目录 文件夹链接会导致功能不能用\n run(f'''rm -rf {install_path}/sd_main_dir/textual_inversion''')\n run(f'''mkdir -p {output_path}/textual_inversion/''')\n run(f'''ln -s -r {output_path}/textual_inversion {install_path}/sd_main_dir/''')\n print('链接输出目录 完成') \n\ndef install_optimizing():\n run('sudo apt install nginx -y')\n run('env TF_CPP_MIN_LOG_LEVEL=1')\n run('apt -y update -qq')\n run('wget http://launchpadlibrarian.net/367274644/libgoogle-perftools-dev_2.5-2.2ubuntu3_amd64.deb')\n run('wget https://launchpad.net/ubuntu/+source/google-perftools/2.5-2.2ubuntu3/+build/14795286/+files/google-perftools_2.5-2.2ubuntu3_all.deb')\n run('wget https://launchpad.net/ubuntu/+source/google-perftools/2.5-2.2ubuntu3/+build/14795286/+files/libtcmalloc-minimal4_2.5-2.2ubuntu3_amd64.deb')\n run('wget https://launchpad.net/ubuntu/+source/google-perftools/2.5-2.2ubuntu3/+build/14795286/+files/libgoogle-perftools4_2.5-2.2ubuntu3_amd64.deb')\n run('apt -y install -qq libunwind8-dev')\n run('dpkg -i *.deb')\n run('env LD_P_reLoad=libtcmalloc.so')\n run('rm *.deb')\n \n#安装依赖\ndef install_dependencies():\n print('安装webui需要的python环境')\n global envInstalled\n global venvPath\n run(f'''rm -rf {install_path}/sd_main_dir/venv''')\n run(f'''mkdir -p {install_path}/sd_main_dir/venv''')\n if str(sys.version).startswith('3.10'):\n try:\n run('python3 -m venv venv' ,cwd=f'{install_path}/sd_main_dir',try_error=False)\n except:\n run('apt install python3.10-venv -y')\n run('python3 -m venv venv' ,cwd=f'{install_path}/sd_main_dir')\n else:\n run('add-apt-repository ppa:deadsnakes/ppa -y')\n run('apt update')\n run('apt install python3.10 -y')\n run('python3.10 -m venv venv',cwd=f'{install_path}/sd_main_dir')\n \n if quickStart:\n if not Path(venvPath).exists():\n run(f'''mkdir -p {install_path}/venv_cache''')\n if not Path(f'{install_path}/venv_cache/venv.tar.bak').exists():\n download_file('https://huggingface.co/viyi/sdwui/resolve/main/venv.zip','venv.zip',f'{install_path}/venv_cache')\n run(f'''unzip {install_path}/venv_cache/venv.zip -d {install_path}/venv_cache''')\n venvPath = f'{install_path}/venv_cache/venv.tar.bak'\n run(f'''rm -rf {install_path}/venv_cache/venv.zip''')\n elif venvPath.endswith('.zip'):\n run(f'''mkdir -p {install_path}/venv_cache''')\n run(f'''unzip {venvPath} -d {install_path}/venv_cache''')\n venvPath = f'{install_path}/venv_cache/venv.tar.bak'\n print('解压环境')\n run(f'tar -xf {venvPath} -C ./venv')\n if not Path(f'{install_path}/sd_main_dir/venv/bin/pip').exists():\n run('curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py')\n run(f'{install_path}/sd_main_dir/venv/bin/python3 get-pip.py')\n\n run(f'''{install_path}/sd_main_dir/venv/bin/python3 -V''')\n run(f'''{install_path}/sd_main_dir/venv/bin/python3 -m pip -V''')\n\n envInstalled = True\n print('安装webui需要的python环境 完成')\n \n# 个性化配置 \ndef use_config():\n print('使用自定义配置 包括tag翻译 \\n')\n run(f'''mkdir -p {install_path}/temp''')\n run(f'git clone {_webui_config_git_repu} sd-configs',cwd=f'{install_path}/temp')\n run(f'cp -r -f -n {install_path}/temp/sd-configs/dist/* {install_path}/sd_main_dir')\n if not Path(_ui_config_file).exists(): # ui配置文件\n run(f'''mkdir -p {_ui_config_file[:_ui_config_file.rfind('/')]}''')\n run(f'cp -f -n {install_path}/sd_main_dir/ui-config.json {_ui_config_file}')\n if not Path(_setting_file).exists(): # 设置配置文件\n run(f'''mkdir -p {_setting_file[:_setting_file.rfind('/')]}''')\n run(f'cp -f -n {install_path}/sd_main_dir/config.json {_setting_file}')\n\ndef copy_last_log_to_images():\n print('复制编号最大的一张收藏图到输出目录,用于保持编号,否则会出现收藏的图片被覆盖的情况')\n img_list = os.listdir(f'{install_path}/sd_main_dir/log/images')\n last_img_path = ''\n last_img_num = 0\n for img in img_list:\n if re.findall(r'^\\d+-',str(img)):\n num = int(re.findall(r'^\\d+-',str(img))[0][:-1])\n if num > last_img_num:\n last_img_path = img\n last_img_num = num\n print(f'{install_path}/sd_main_dir/log/images/{last_img_path} {install_path}/sd_main_dir/outputs/txt2img-images')\n run(f'''mkdir -p {install_path}/sd_main_dir/outputs/txt2img-images''')\n run(f'''cp -f {install_path}/sd_main_dir/log/images/{last_img_path} {install_path}/sd_main_dir/outputs/txt2img-images/''')\n \n print(f'{install_path}/sd_main_dir/log/images/{last_img_path} {install_path}/sd_main_dir/outputs/img2img-images')\n run(f'''mkdir -p {install_path}/sd_main_dir/outputs/img2img-images''')\n run(f'''cp -f {install_path}/sd_main_dir/log/images/{last_img_path} {install_path}/sd_main_dir/outputs/img2img-images/''')\n \ndef start_webui(i):\n # 只要不爆内存,其他方式关闭后会再次重启 访问地址会发生变化\n print(i,'--port',str(_webuiPort+1+i))\n if i>0:\n print(f'使用第{i+1}张显卡启动第{i+1}个webui,通过frpc或nrgok地址后加/{i}/进行访问(不能使用同一个浏览器)')\n if _useFrpc:\n restart_times = 0\n last_restart_time = time.time()\n while True:\n get_ipython().system(f'''venv/bin/python3 launch.py --device-id={i} --port {str(_webuiPort+1+i)} {'' if i ==0 else '--nowebui'}''')\n print('5秒后重启webui')\n if time.time() - last_restart_time < 30:\n restart_times = restart_times + 1\n else:\n restart_times = 0\n last_restart_time = time.time()\n if restart_times >3 :\n # 如果90秒内重启了3此,将不再自动重启\n break\n time.sleep(5)\n else:\n get_ipython().system(f'''venv/bin/python3 launch.py --device-id={i} --port {str(_webuiPort+1+i)} {'' if i ==0 else '--nowebui'}''')\n \n# 启动\ndef start():\n print('启动webui')\n os.chdir(f'''{install_path}/sd_main_dir''')\n args = ''\n if _ui_config_file is not None and _ui_config_file != '' and Path(_ui_config_file).exists(): # ui配置文件\n args += ' --ui-config-file=' + _ui_config_file\n if _setting_file is not None and _setting_file != '' and Path(_setting_file).exists(): # 设置配置文件\n args += ' --ui-settings-file=' + _setting_file\n args += ' ' + otherArgs\n os.environ['COMMANDLINE_ARGS']=args\n run(f'''echo COMMANDLINE_ARGS=$COMMANDLINE_ARGS''')\n os.environ['REQS_FILE']='requirements.txt'\n\n with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:\n for i in range(torch.cuda.device_count() if '多实例' in globals() and 多实例 else 1):\n executor.submit(start_webui,i)\n while not check_service('localhost',str(_webuiPort+1+i)): # 当当前服务启动完成才允许退出此次循环\n time.sleep(5)\n time.sleep(10)","metadata":{"_kg_hide-input":true,"id":"GTjyBJihLCtE","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 入口函数\n---","metadata":{"id":"qLvsk8ByLCtF"}},{"cell_type":"code","source":"\n# 启动非webui相关的的内容,加快启动速度\ndef main():\n global envInstalled\n global huggingface_is_init\n startTicks = time.time()\n stop_solo_threads()\n isInstall = True if os.getenv('IsInstall','False') == 'True' else False\n if isInstall is False or _reLoad: \n print('启动 安装webui和运行环境')\n install()\n link_dir()\n init_huggingface()\n threading.Thread(target = install_dependencies,daemon=True,name='solo_install_dependencies').start()\n threading.Thread(target = install_optimizing,daemon=True,name='solo_install_optimizing').start()\n threading.Thread(target = installProxyExe,daemon=True).start()\n link_or_download_flie(replace_path(_普通文件列表), _link_instead_of_copy=_link_instead_of_copy,\n base_path=f'{install_path}/sd_main_dir')\n if huggingface_is_init:\n threading.Thread(target = download__huggingface_repo,daemon=True,\n args=([_huggingface_repo]),\n kwargs={\"callback\":copy_last_log_to_images},\n name='solo_download__huggingface_repo').start()\n \n link_or_download_flie(replace_path(_重要文件列表), _link_instead_of_copy=_link_instead_of_copy,\n base_path=f'{install_path}/sd_main_dir',is_await=True)\n t = 0\n while not envInstalled:\n if t%10==0:\n print('等待python环境安装...')\n t = t+1\n time.sleep(1)\n use_config()\n localProxy()\n os.environ['IsInstall'] = 'True'\n else:\n envInstalled = True\n link_or_download_flie(replace_path(_按顺序加载的重要文件列表), _link_instead_of_copy=_link_instead_of_copy,\n base_path=f'{install_path}/sd_main_dir',sync=True)\n threading.Thread(target = startProxy, daemon=True, name='solo_startProxy').start()\n if init_huggingface():\n start_sync_log_to_huggingface(_huggingface_repo)\n ticks = time.time()\n print(\"加载耗时:\",(ticks - startTicks),\"秒\")\n start()\n","metadata":{"_kg_hide-input":true,"id":"IOKjaMlcLCtF","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 执行区域\n---","metadata":{"id":"0oaCRs2gLCtF"}},{"cell_type":"code","source":"print(update_desc)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 启动\n# _reLoad = True\n# hidden_console_info = False\n# run_by_none_device = True\n# show_shell_info = True\ntry:\n check_gpu()\n main()\nexcept KeyboardInterrupt:\n stop_solo_threads()","metadata":{"_kg_hide-output":true,"id":"O3DR0DWHLCtF","scrolled":true,"trusted":true},"execution_count":null,"outputs":[]}]}