factorstudios commited on
Commit
ae8e699
Β·
verified Β·
1 Parent(s): 2a11ebf

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +231 -0
app.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import time
4
+ import tempfile
5
+ import shutil
6
+ from pathlib import Path
7
+ from huggingface_hub import hf_hub_download, upload_file, list_repo_files
8
+ from fastapi import FastAPI
9
+ from contextlib import asynccontextmanager
10
+ import asyncio
11
+ import logging
12
+
13
+ try:
14
+ import rarfile
15
+ except ImportError:
16
+ rarfile = None
17
+
18
+ # === CONFIGURATION ===
19
+ HF_TOKEN = os.environ.get("HF_TOKEN")
20
+ REPO_ID = "factorstudios/Pipeline"
21
+ DATA_PATH = "Blenders"
22
+ EXTRACTED_PATH = "Blenders/extracted"
23
+ TEMP_DIR = tempfile.gettempdir()
24
+
25
+ # === Setup Logging ===
26
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
27
+
28
+ app = FastAPI()
29
+
30
+ # === Health Check Routes ===
31
+ @app.get("/")
32
+ def root():
33
+ return {"status": "RAR Extractor running"}
34
+
35
+ @app.get("/health")
36
+ def health():
37
+ return {"healthy": True}
38
+
39
+ # === Get Course Name from Filename ===
40
+ def extract_course_name(filename: str) -> str:
41
+ """Extract course name from RAR filename"""
42
+ name = os.path.splitext(filename)[0]
43
+ # Remove common patterns like .part1, .001, etc
44
+ name = re.sub(r'\.(part\d+|r\d+|\d+)$', '', name, flags=re.IGNORECASE)
45
+ return name
46
+
47
+ # === Download File from Dataset ===
48
+ def download_from_dataset(filename: str, repo_path: str) -> str:
49
+ try:
50
+ logging.info(f"[*] Downloading from dataset: {repo_path}/{filename}")
51
+ local_path = hf_hub_download(
52
+ repo_id=REPO_ID,
53
+ filename=f"{repo_path}/{filename}",
54
+ repo_type="dataset",
55
+ token=HF_TOKEN,
56
+ cache_dir=TEMP_DIR
57
+ )
58
+ logging.info(f"[βœ“] Downloaded: {filename}")
59
+ return local_path
60
+ except Exception as e:
61
+ logging.error(f"[!] Download failed: {filename} β€” {e}")
62
+ return None
63
+
64
+ # === Extract RAR File ===
65
+ def extract_rar(rar_path: str, extract_dir: str) -> bool:
66
+ try:
67
+ if rarfile is None:
68
+ logging.error("[!] rarfile module not installed. Install with: pip install rarfile")
69
+ return False
70
+
71
+ logging.info(f"[*] Extracting RAR: {os.path.basename(rar_path)}")
72
+ with rarfile.RarFile(rar_path) as rf:
73
+ rf.extractall(extract_dir)
74
+ logging.info(f"[βœ“] Extracted to: {extract_dir}")
75
+ return True
76
+ except Exception as e:
77
+ logging.error(f"[!] RAR extraction failed: {rar_path} β€” {e}")
78
+ return False
79
+
80
+ # === Upload Directory Contents to Dataset ===
81
+ def upload_directory_to_dataset(local_dir: str, dataset_path: str) -> bool:
82
+ try:
83
+ file_count = 0
84
+ for root, dirs, files in os.walk(local_dir):
85
+ for file in files:
86
+ filepath = os.path.join(root, file)
87
+ relative_path = os.path.relpath(filepath, local_dir)
88
+ remote_path = f"{dataset_path}/{relative_path}".replace("\\", "/")
89
+
90
+ upload_file(
91
+ path_or_fileobj=filepath,
92
+ path_in_repo=remote_path,
93
+ repo_id=REPO_ID,
94
+ repo_type="dataset",
95
+ token=HF_TOKEN
96
+ )
97
+ logging.info(f"[↑] Uploaded: {remote_path}")
98
+ file_count += 1
99
+
100
+ logging.info(f"[βœ“] Uploaded {file_count} files to {dataset_path}")
101
+ return True
102
+ except Exception as e:
103
+ logging.error(f"[!] Upload directory failed: {local_dir} β€” {e}")
104
+ return False
105
+
106
+ # === List RAR Files in Dataset ===
107
+ def list_rar_files_in_dataset() -> list:
108
+ try:
109
+ logging.info(f"[*] Scanning dataset for RAR files in {DATA_PATH}")
110
+ all_files = list_repo_files(
111
+ repo_id=REPO_ID,
112
+ repo_type="dataset",
113
+ token=HF_TOKEN
114
+ )
115
+
116
+ rar_files = [
117
+ f for f in all_files
118
+ if f.startswith(DATA_PATH)
119
+ and (f.lower().endswith('.rar') or re.search(r'\.r\d{2}$', f, re.IGNORECASE))
120
+ ]
121
+
122
+ logging.info(f"[*] Found {len(rar_files)} RAR files")
123
+ for rf in rar_files:
124
+ logging.info(f" - {rf}")
125
+
126
+ return rar_files
127
+ except Exception as e:
128
+ logging.error(f"[!] Failed to list files: {e}")
129
+ return []
130
+
131
+ # === Extract and Upload RAR ===
132
+ async def extract_and_upload_rar(rar_file_path: str):
133
+ try:
134
+ # Get filename
135
+ filename = os.path.basename(rar_file_path)
136
+ course_name = extract_course_name(filename)
137
+
138
+ # Create temp extraction directory
139
+ extract_dir = os.path.join(TEMP_DIR, f"rar_extract_{int(time.time())}")
140
+ os.makedirs(extract_dir, exist_ok=True)
141
+
142
+ logging.info(f"[*] Processing: {filename} (Course: {course_name})")
143
+
144
+ # Download RAR file
145
+ local_rar = download_from_dataset(filename, DATA_PATH)
146
+ if not local_rar:
147
+ return False
148
+
149
+ # Extract RAR
150
+ if not extract_rar(local_rar, extract_dir):
151
+ shutil.rmtree(extract_dir, ignore_errors=True)
152
+ return False
153
+
154
+ # Upload to dataset under blenders/extracted/{course_name}
155
+ remote_path = f"{EXTRACTED_PATH}/{course_name}"
156
+ if not upload_directory_to_dataset(extract_dir, remote_path):
157
+ shutil.rmtree(extract_dir, ignore_errors=True)
158
+ return False
159
+
160
+ # Cleanup
161
+ shutil.rmtree(extract_dir, ignore_errors=True)
162
+ logging.info(f"[βœ“] Completed: {course_name}")
163
+ return True
164
+
165
+ except Exception as e:
166
+ logging.error(f"[!] Error processing {rar_file_path}: {e}")
167
+ return False
168
+
169
+ # === Background Worker ===
170
+ async def rar_processor_worker():
171
+ logging.info("πŸš€ RAR Processor started")
172
+ while True:
173
+ try:
174
+ logging.info("[*] Scanning for RAR files...")
175
+ rar_files = list_rar_files_in_dataset()
176
+
177
+ if rar_files:
178
+ for rar_file in rar_files:
179
+ await extract_and_upload_rar(rar_file)
180
+ await asyncio.sleep(5) # Delay between files
181
+ else:
182
+ logging.info("[*] No RAR files found, waiting...")
183
+
184
+ # Wait 60 seconds before next scan
185
+ await asyncio.sleep(60)
186
+ except Exception as e:
187
+ logging.error(f"[!] Worker error: {e}")
188
+ await asyncio.sleep(60)
189
+
190
+ # === FastAPI Lifespan ===
191
+ @asynccontextmanager
192
+ async def lifespan(app: FastAPI):
193
+ logging.info("πŸš€ Starting RAR Extractor FastAPI server...")
194
+ task = asyncio.create_task(rar_processor_worker())
195
+ yield
196
+ task.cancel()
197
+ logging.info("πŸ›‘ Shutting down RAR Extractor.")
198
+
199
+ # === Update App with Lifespan ===
200
+ app = FastAPI(lifespan=lifespan)
201
+
202
+ # === API Endpoints ===
203
+ @app.get("/")
204
+ def root():
205
+ return {"status": "RAR Extractor running"}
206
+
207
+ @app.get("/health")
208
+ def health():
209
+ return {"healthy": True}
210
+
211
+ @app.get("/scan")
212
+ def scan_rars():
213
+ """Manually trigger RAR file scan"""
214
+ rar_files = list_rar_files_in_dataset()
215
+ return {"found": len(rar_files), "files": rar_files}
216
+
217
+ @app.post("/extract-all")
218
+ async def extract_all():
219
+ """Manually trigger extraction of all RAR files"""
220
+ rar_files = list_rar_files_in_dataset()
221
+
222
+ if not rar_files:
223
+ return {"message": "No RAR files found"}
224
+
225
+ results = []
226
+ for rar_file in rar_files:
227
+ success = await extract_and_upload_rar(rar_file)
228
+ results.append({"file": rar_file, "success": success})
229
+ await asyncio.sleep(5)
230
+
231
+ return {"processed": len(results), "results": results}