Upload main.py
Browse files
main.py
CHANGED
@@ -14,7 +14,6 @@ import requests
|
|
14 |
from fastapi import FastAPI
|
15 |
from huggingface_hub import HfApi, hf_hub_download, login
|
16 |
|
17 |
-
# ロギングの設定
|
18 |
logging.basicConfig(level=logging.INFO)
|
19 |
logger = logging.getLogger(__name__)
|
20 |
|
@@ -48,6 +47,7 @@ class Config:
|
|
48 |
# 暗号化されたファイルが出力されるローカルディレクトリ(cryptLocal: の実体)
|
49 |
ENCRYPTED_DIR = "/home/user/app/encrypted"
|
50 |
|
|
|
51 |
class CivitAICrawler:
|
52 |
"""CivitAIからモデルをダウンロードし、Hugging Faceにアップロードするクラス(フォルダ名も暗号化対応版)"""
|
53 |
|
@@ -58,7 +58,7 @@ class CivitAICrawler:
|
|
58 |
self.repo_ids = self.config.REPO_IDS.copy()
|
59 |
self.jst = self.config.JST
|
60 |
|
61 |
-
# rclone
|
62 |
self.setup_rclone_conf()
|
63 |
|
64 |
self.setup_routes()
|
@@ -69,7 +69,7 @@ class CivitAICrawler:
|
|
69 |
now = str(datetime.datetime.now(self.jst))
|
70 |
description = f"""
|
71 |
CivitAIを定期的に周回し新規モデルを {self.repo_ids['current']} にバックアップするSpaceです。
|
72 |
-
|
73 |
Status: {now} + currently running :D
|
74 |
"""
|
75 |
return description
|
@@ -97,8 +97,12 @@ class CivitAICrawler:
|
|
97 |
|
98 |
def encrypt_with_rclone(self, local_path: str):
|
99 |
"""
|
100 |
-
指定ファイル or ディレクトリを cryptLocal
|
101 |
-
|
|
|
|
|
|
|
|
|
102 |
"""
|
103 |
if not os.path.exists(local_path):
|
104 |
raise FileNotFoundError(f"[ERROR] Local path not found: {local_path}")
|
@@ -107,10 +111,17 @@ class CivitAICrawler:
|
|
107 |
if os.path.isdir(self.config.ENCRYPTED_DIR):
|
108 |
shutil.rmtree(self.config.ENCRYPTED_DIR, ignore_errors=True)
|
109 |
|
110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
logger.info(f"[INFO] Running: {' '.join(cmd)}")
|
112 |
subprocess.run(cmd, check=True)
|
113 |
-
logger.info(f"[OK] rclone copy => cryptLocal:")
|
114 |
|
115 |
if not os.path.isdir(self.config.ENCRYPTED_DIR):
|
116 |
raise FileNotFoundError(
|
@@ -119,23 +130,25 @@ class CivitAICrawler:
|
|
119 |
|
120 |
def upload_encrypted_files(self, repo_id: str, base_path_in_repo: str = ""):
|
121 |
"""
|
122 |
-
self.config.ENCRYPTED_DIR
|
123 |
-
Hugging Face
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
"""
|
125 |
max_retries = 5
|
126 |
|
127 |
-
# 再帰的に暗号化フォルダを探索
|
128 |
for root, dirs, files in os.walk(self.config.ENCRYPTED_DIR):
|
129 |
for fn in files:
|
130 |
encrypted_file_path = os.path.join(root, fn)
|
131 |
if not os.path.isfile(encrypted_file_path):
|
132 |
continue
|
133 |
|
134 |
-
# self.config.ENCRYPTED_DIR
|
135 |
relative_path = os.path.relpath(encrypted_file_path, self.config.ENCRYPTED_DIR)
|
136 |
-
# Hugging Face上にアップロードする際のディレクトリパス
|
137 |
-
# ここ��は base_path_in_repo + relative_path にしているが、
|
138 |
-
# base_path_in_repo が空("")なら、rclone で暗号化されたディレクトリ名をそのまま使う
|
139 |
upload_path_in_repo = os.path.join(base_path_in_repo, relative_path)
|
140 |
|
141 |
attempt = 0
|
@@ -162,7 +175,7 @@ class CivitAICrawler:
|
|
162 |
elif "you can retry this action in about 1 hour" in error_message:
|
163 |
logger.warning("Encountered 'retry in 1 hour' error. Waiting 1 hour before retrying...")
|
164 |
time.sleep(3600)
|
165 |
-
attempt -= 1
|
166 |
else:
|
167 |
if attempt < max_retries:
|
168 |
logger.warning(
|
@@ -347,38 +360,37 @@ class CivitAICrawler:
|
|
347 |
with open(os.path.join(folder, "model_info.json"), "w") as file:
|
348 |
json.dump(model_info, file, indent=2)
|
349 |
|
|
|
|
|
|
|
350 |
@staticmethod
|
351 |
def increment_repo_name(repo_id: str) -> str:
|
352 |
-
"""リポジトリ名の末尾の数字をインクリメントする。"""
|
353 |
match = re.search(r'(\d+)$', repo_id)
|
354 |
if match:
|
355 |
number = int(match.group(1)) + 1
|
356 |
-
|
357 |
else:
|
358 |
-
|
359 |
-
return new_repo_id
|
360 |
|
361 |
# =============================================================================
|
362 |
-
#
|
363 |
# =============================================================================
|
364 |
def upload_file(self, file_path: str):
|
365 |
"""
|
366 |
-
|
367 |
-
|
368 |
-
(ファイル名もフォルダ名も暗号化される)
|
369 |
"""
|
370 |
-
#
|
371 |
-
self.encrypt_with_rclone(file_path)
|
372 |
-
# 暗号化フォルダ全体をアップロード (フォルダ名も暗号化される)
|
373 |
self.upload_encrypted_files(repo_id=self.repo_ids['current'], base_path_in_repo="")
|
374 |
-
# 後始末
|
375 |
if os.path.isdir(self.config.ENCRYPTED_DIR):
|
376 |
shutil.rmtree(self.config.ENCRYPTED_DIR, ignore_errors=True)
|
377 |
|
378 |
def upload_folder(self, folder_path: str):
|
379 |
"""
|
380 |
-
|
381 |
-
|
|
|
|
|
382 |
"""
|
383 |
self.encrypt_with_rclone(folder_path)
|
384 |
self.upload_encrypted_files(repo_id=self.repo_ids['current'], base_path_in_repo="")
|
|
|
14 |
from fastapi import FastAPI
|
15 |
from huggingface_hub import HfApi, hf_hub_download, login
|
16 |
|
|
|
17 |
logging.basicConfig(level=logging.INFO)
|
18 |
logger = logging.getLogger(__name__)
|
19 |
|
|
|
47 |
# 暗号化されたファイルが出力されるローカルディレクトリ(cryptLocal: の実体)
|
48 |
ENCRYPTED_DIR = "/home/user/app/encrypted"
|
49 |
|
50 |
+
|
51 |
class CivitAICrawler:
|
52 |
"""CivitAIからモデルをダウンロードし、Hugging Faceにアップロードするクラス(フォルダ名も暗号化対応版)"""
|
53 |
|
|
|
58 |
self.repo_ids = self.config.REPO_IDS.copy()
|
59 |
self.jst = self.config.JST
|
60 |
|
61 |
+
# rclone のセットアップ
|
62 |
self.setup_rclone_conf()
|
63 |
|
64 |
self.setup_routes()
|
|
|
69 |
now = str(datetime.datetime.now(self.jst))
|
70 |
description = f"""
|
71 |
CivitAIを定期的に周回し新規モデルを {self.repo_ids['current']} にバックアップするSpaceです。
|
72 |
+
モデル名とバックアップURLの紐づきは: https://huggingface.co/{self.repo_ids['model_list']}/blob/main/model_list.log
|
73 |
Status: {now} + currently running :D
|
74 |
"""
|
75 |
return description
|
|
|
97 |
|
98 |
def encrypt_with_rclone(self, local_path: str):
|
99 |
"""
|
100 |
+
指定ファイル or ディレクトリを cryptLocal:にコピー。
|
101 |
+
このとき「トップレベルのフォルダ名」も暗号化して保持するため、
|
102 |
+
cryptLocal:{os.path.basename(local_path)} の形でコピーする。
|
103 |
+
|
104 |
+
※ rclone の crypt設定が filename_encryption = standard 等なら、
|
105 |
+
フォルダ名やファイル名も丸ごと暗号化される
|
106 |
"""
|
107 |
if not os.path.exists(local_path):
|
108 |
raise FileNotFoundError(f"[ERROR] Local path not found: {local_path}")
|
|
|
111 |
if os.path.isdir(self.config.ENCRYPTED_DIR):
|
112 |
shutil.rmtree(self.config.ENCRYPTED_DIR, ignore_errors=True)
|
113 |
|
114 |
+
# コピー先を cryptLocal:xxx に指定することで、xxx というフォルダ単位で
|
115 |
+
# 暗号化されたディレクトリを作成できるようにする。
|
116 |
+
top_level_name = os.path.basename(local_path.rstrip("/"))
|
117 |
+
if not top_level_name:
|
118 |
+
top_level_name = "unknown"
|
119 |
+
|
120 |
+
# rclone copy local_path -> cryptLocal:top_level_name
|
121 |
+
cmd = ["rclone", "copy", local_path, f"cryptLocal:{top_level_name}", "-v"]
|
122 |
logger.info(f"[INFO] Running: {' '.join(cmd)}")
|
123 |
subprocess.run(cmd, check=True)
|
124 |
+
logger.info(f"[OK] rclone copy => cryptLocal:{top_level_name}")
|
125 |
|
126 |
if not os.path.isdir(self.config.ENCRYPTED_DIR):
|
127 |
raise FileNotFoundError(
|
|
|
130 |
|
131 |
def upload_encrypted_files(self, repo_id: str, base_path_in_repo: str = ""):
|
132 |
"""
|
133 |
+
self.config.ENCRYPTED_DIR にある暗号化後のファイル/フォルダ構造を
|
134 |
+
そのまま Hugging Face にアップロードする。
|
135 |
+
|
136 |
+
rclone で filename_encryption=standard が有効な場合:
|
137 |
+
- 元フォルダ名/ファイル名は完全に暗号化され、HF上では判読不能な名称になる。
|
138 |
+
|
139 |
+
base_path_in_repo が空("")の場合は、
|
140 |
+
`/home/user/app/encrypted` の構造が HF リポジトリの直下に展開される。
|
141 |
"""
|
142 |
max_retries = 5
|
143 |
|
|
|
144 |
for root, dirs, files in os.walk(self.config.ENCRYPTED_DIR):
|
145 |
for fn in files:
|
146 |
encrypted_file_path = os.path.join(root, fn)
|
147 |
if not os.path.isfile(encrypted_file_path):
|
148 |
continue
|
149 |
|
150 |
+
# self.config.ENCRYPTED_DIR からの相対パス(暗号化後のフォルダ名・ファイル名)
|
151 |
relative_path = os.path.relpath(encrypted_file_path, self.config.ENCRYPTED_DIR)
|
|
|
|
|
|
|
152 |
upload_path_in_repo = os.path.join(base_path_in_repo, relative_path)
|
153 |
|
154 |
attempt = 0
|
|
|
175 |
elif "you can retry this action in about 1 hour" in error_message:
|
176 |
logger.warning("Encountered 'retry in 1 hour' error. Waiting 1 hour before retrying...")
|
177 |
time.sleep(3600)
|
178 |
+
attempt -= 1
|
179 |
else:
|
180 |
if attempt < max_retries:
|
181 |
logger.warning(
|
|
|
360 |
with open(os.path.join(folder, "model_info.json"), "w") as file:
|
361 |
json.dump(model_info, file, indent=2)
|
362 |
|
363 |
+
# =============================================================================
|
364 |
+
# 以下はダウンロードやモデル情報処理の部分(元コードと同等)
|
365 |
+
# =============================================================================
|
366 |
@staticmethod
|
367 |
def increment_repo_name(repo_id: str) -> str:
|
|
|
368 |
match = re.search(r'(\d+)$', repo_id)
|
369 |
if match:
|
370 |
number = int(match.group(1)) + 1
|
371 |
+
return re.sub(r'\d+$', str(number), repo_id)
|
372 |
else:
|
373 |
+
return f"{repo_id}1"
|
|
|
374 |
|
375 |
# =============================================================================
|
376 |
+
# フォルダアップロード
|
377 |
# =============================================================================
|
378 |
def upload_file(self, file_path: str):
|
379 |
"""
|
380 |
+
単一ファイルを暗号化 → 暗号化後のものをHugging Faceへアップロード。
|
381 |
+
rcloneでフォルダを作って暗号化するため、フォルダ構造としてアップされる。
|
|
|
382 |
"""
|
383 |
+
self.encrypt_with_rclone(file_path) # 1) ローカルを -> cryptLocal:basename(…)
|
|
|
|
|
384 |
self.upload_encrypted_files(repo_id=self.repo_ids['current'], base_path_in_repo="")
|
|
|
385 |
if os.path.isdir(self.config.ENCRYPTED_DIR):
|
386 |
shutil.rmtree(self.config.ENCRYPTED_DIR, ignore_errors=True)
|
387 |
|
388 |
def upload_folder(self, folder_path: str):
|
389 |
"""
|
390 |
+
フォルダを暗号化 → フォルダ名ごとアップロード。
|
391 |
+
rclone copy folder_path => cryptLocal:folder_pathのbasename
|
392 |
+
すると {ENCRYPTED_DIR}/{暗号化後のトップレベルフォルダ} が生成されるため、
|
393 |
+
それを再帰的にHugging Faceへアップロードする。
|
394 |
"""
|
395 |
self.encrypt_with_rclone(folder_path)
|
396 |
self.upload_encrypted_files(repo_id=self.repo_ids['current'], base_path_in_repo="")
|