Update main.py
Browse files
main.py
CHANGED
|
@@ -164,6 +164,172 @@ def trigger_kaggle_test():
|
|
| 164 |
|
| 165 |
return "\n".join(logs)
|
| 166 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
with gr.Blocks() as app:
|
| 168 |
gr.Markdown("# YouTube Video Orchestrator Host")
|
| 169 |
gr.Markdown("Deploy this to Hugging Face Spaces or run locally. It fetches pending links from your database and delegates them to FastAPI workers or Kaggle.")
|
|
@@ -182,10 +348,18 @@ with gr.Blocks() as app:
|
|
| 182 |
|
| 183 |
with gr.Tab("Kaggle Workers"):
|
| 184 |
gr.Markdown("Trigger Kaggle notebook workers. Note: You must mount `kaggle.json` to `~/.kaggle/kaggle.json` in the container for this to work.")
|
| 185 |
-
test_run_btn = gr.Button("Run Simple Kaggle Test Worker", variant="secondary")
|
| 186 |
-
kaggle_log = gr.Textbox(label="Kaggle Logs", lines=10)
|
| 187 |
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
|
| 190 |
if __name__ == "__main__":
|
| 191 |
app.launch(server_name="0.0.0.0", server_port=7860)
|
|
|
|
| 164 |
|
| 165 |
return "\n".join(logs)
|
| 166 |
|
| 167 |
+
def create_worker_notebook(worker_dir, config_name):
|
| 168 |
+
notebook_content = {
|
| 169 |
+
"cells": [
|
| 170 |
+
{
|
| 171 |
+
"cell_type": "code",
|
| 172 |
+
"execution_count": None,
|
| 173 |
+
"metadata": {},
|
| 174 |
+
"outputs": [],
|
| 175 |
+
"source": [
|
| 176 |
+
"!pip install openai huggingface_hub yt-dlp opencv-python-headless\n"
|
| 177 |
+
]
|
| 178 |
+
},
|
| 179 |
+
{
|
| 180 |
+
"cell_type": "code",
|
| 181 |
+
"execution_count": None,
|
| 182 |
+
"metadata": {},
|
| 183 |
+
"outputs": [],
|
| 184 |
+
"source": [
|
| 185 |
+
"import json\n",
|
| 186 |
+
"import os\n",
|
| 187 |
+
"import subprocess\n",
|
| 188 |
+
"import sys\n",
|
| 189 |
+
"\n",
|
| 190 |
+
f"with open('{config_name}') as f:\n",
|
| 191 |
+
" config = json.load(f)\n",
|
| 192 |
+
"\n",
|
| 193 |
+
"links = config['links']\n",
|
| 194 |
+
"api_key = config['api_key']\n",
|
| 195 |
+
"os.environ['FEATHERLESS_API_KEY'] = api_key\n",
|
| 196 |
+
"\n",
|
| 197 |
+
"print(f'Starting worker with {len(links)} links using API Key: {api_key[:5]}...')\n",
|
| 198 |
+
"\n",
|
| 199 |
+
"for link in links:\n",
|
| 200 |
+
" print(f'\\n[+] Processing: {link}')\n",
|
| 201 |
+
" # 1. Download Video\n",
|
| 202 |
+
" dl_cmd = [sys.executable, 'worker/youtube_worker/downloader.py', link, '--resolution', 'HD', '-o', 'test_output']\n",
|
| 203 |
+
" res = subprocess.run(dl_cmd, capture_output=True, text=True)\n",
|
| 204 |
+
" if res.returncode != 0:\n",
|
| 205 |
+
" print(f'Download failed:\\n{res.stderr}')\n",
|
| 206 |
+
" continue\n",
|
| 207 |
+
" \n",
|
| 208 |
+
" # Find the downloaded video path from stdout (last line)\n",
|
| 209 |
+
" lines = res.stdout.strip().split('\\n')\n",
|
| 210 |
+
" video_path = lines[-1]\n",
|
| 211 |
+
" if not os.path.exists(video_path):\n",
|
| 212 |
+
" print(f'Could not find downloaded video at: {video_path}')\n",
|
| 213 |
+
" continue\n",
|
| 214 |
+
" \n",
|
| 215 |
+
" print(f'[+] Video downloaded to: {video_path}')\n",
|
| 216 |
+
" \n",
|
| 217 |
+
" # 2. Curate Model (Video Worker)\n",
|
| 218 |
+
" prompt = 'Analyze this video comprehensively. Focus on visuals, objects, and text.'\n",
|
| 219 |
+
" vw_cmd = [sys.executable, 'worker/models_worker/video_worker.py', '--video-url', video_path, '--prompt', prompt, '--api-key', api_key]\n",
|
| 220 |
+
" print('[+] Running model curation...')\n",
|
| 221 |
+
" subprocess.run(vw_cmd)\n",
|
| 222 |
+
" \n",
|
| 223 |
+
" # 3. Optional: Upload to HF\n",
|
| 224 |
+
" # hf_cmd = [sys.executable, 'worker/hf_uploader.py', 'test_output']\n",
|
| 225 |
+
" # subprocess.run(hf_cmd)\n",
|
| 226 |
+
"\n",
|
| 227 |
+
"print('\\nAll tasks completed!')\n"
|
| 228 |
+
]
|
| 229 |
+
}
|
| 230 |
+
],
|
| 231 |
+
"metadata": {
|
| 232 |
+
"kernelspec": {
|
| 233 |
+
"display_name": "Python 3",
|
| 234 |
+
"language": "python",
|
| 235 |
+
"name": "python3"
|
| 236 |
+
},
|
| 237 |
+
"language_info": {
|
| 238 |
+
"name": "python"
|
| 239 |
+
}
|
| 240 |
+
},
|
| 241 |
+
"nbformat": 4,
|
| 242 |
+
"nbformat_minor": 4
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
with open(worker_dir / "notebook.ipynb", "w") as f:
|
| 246 |
+
json.dump(notebook_content, f, indent=2)
|
| 247 |
+
|
| 248 |
+
def trigger_kaggle_workers(db_path, links_per_worker, max_workers):
|
| 249 |
+
api_keys, links = fetch_data(db_path, limit_links=int(links_per_worker) * int(max_workers))
|
| 250 |
+
if not api_keys:
|
| 251 |
+
return "Error: No API keys found in DB."
|
| 252 |
+
if not links:
|
| 253 |
+
return "Error: No pending links found in DB."
|
| 254 |
+
|
| 255 |
+
username = get_kaggle_username()
|
| 256 |
+
workers_dir = Path("kaggle_workers_out")
|
| 257 |
+
if workers_dir.exists():
|
| 258 |
+
shutil.rmtree(workers_dir)
|
| 259 |
+
workers_dir.mkdir(parents=True)
|
| 260 |
+
|
| 261 |
+
logs = []
|
| 262 |
+
logs.append(f"Loaded {len(api_keys)} API keys and {len(links)} links.")
|
| 263 |
+
|
| 264 |
+
link_chunks = [links[i:i + int(links_per_worker)] for i in range(0, len(links), int(links_per_worker))]
|
| 265 |
+
logs.append(f"Divided into {len(link_chunks)} worker chunks.")
|
| 266 |
+
|
| 267 |
+
for idx, chunk in enumerate(link_chunks):
|
| 268 |
+
worker_id = f"worker-{uuid.uuid4().hex[:8]}"
|
| 269 |
+
worker_title = f"yt-worker-{idx+1}-{uuid.uuid4().hex[:4]}"
|
| 270 |
+
|
| 271 |
+
api_key = api_keys[idx % len(api_keys)]
|
| 272 |
+
worker_path = workers_dir / worker_id
|
| 273 |
+
worker_path.mkdir(parents=True)
|
| 274 |
+
|
| 275 |
+
# Copy worker if exists locally
|
| 276 |
+
if Path("worker").exists():
|
| 277 |
+
shutil.copytree("worker", worker_path / "worker")
|
| 278 |
+
else:
|
| 279 |
+
# We copy from parent directory if this is running locally, otherwise it's an error on HF unless packed
|
| 280 |
+
parent_worker = Path("../hf_space_worker/worker")
|
| 281 |
+
if parent_worker.exists():
|
| 282 |
+
shutil.copytree(parent_worker, worker_path / "worker")
|
| 283 |
+
else:
|
| 284 |
+
logs.append("Warning: 'worker' directory not found. Kaggle notebook may fail.")
|
| 285 |
+
|
| 286 |
+
if Path("cookies.txt").exists():
|
| 287 |
+
shutil.copy("cookies.txt", worker_path / "cookies.txt")
|
| 288 |
+
elif Path("../cookies.txt").exists():
|
| 289 |
+
shutil.copy("../cookies.txt", worker_path / "cookies.txt")
|
| 290 |
+
|
| 291 |
+
config_name = "worker_config.json"
|
| 292 |
+
config_data = {
|
| 293 |
+
"links": chunk,
|
| 294 |
+
"api_key": api_key,
|
| 295 |
+
}
|
| 296 |
+
with open(worker_path / config_name, "w") as f:
|
| 297 |
+
json.dump(config_data, f, indent=2)
|
| 298 |
+
|
| 299 |
+
create_worker_notebook(worker_path, config_name)
|
| 300 |
+
|
| 301 |
+
metadata = {
|
| 302 |
+
"id": f"{username}/{worker_title}",
|
| 303 |
+
"title": worker_title,
|
| 304 |
+
"code_file": "notebook.ipynb",
|
| 305 |
+
"language": "python",
|
| 306 |
+
"kernel_type": "notebook",
|
| 307 |
+
"is_private": True,
|
| 308 |
+
"enable_gpu": False,
|
| 309 |
+
"enable_internet": True,
|
| 310 |
+
"dataset_sources": [],
|
| 311 |
+
"competition_sources": [],
|
| 312 |
+
"kernel_sources": []
|
| 313 |
+
}
|
| 314 |
+
with open(worker_path / "kernel-metadata.json", "w") as f:
|
| 315 |
+
json.dump(metadata, f, indent=2)
|
| 316 |
+
|
| 317 |
+
logs.append(f"Prepared Worker {idx+1}: {worker_title} with {len(chunk)} links.")
|
| 318 |
+
|
| 319 |
+
command = ["kaggle", "kernels", "push", "-p", str(worker_path)]
|
| 320 |
+
logs.append(f"Pushing {worker_title} to Kaggle...")
|
| 321 |
+
try:
|
| 322 |
+
res = subprocess.run(command, capture_output=True, text=True)
|
| 323 |
+
if res.returncode == 0:
|
| 324 |
+
logs.append("Push successful.")
|
| 325 |
+
else:
|
| 326 |
+
logs.append("Push failed.")
|
| 327 |
+
logs.append(res.stderr)
|
| 328 |
+
except Exception as e:
|
| 329 |
+
logs.append(f"Error: {e}")
|
| 330 |
+
|
| 331 |
+
return "\n".join(logs)
|
| 332 |
+
|
| 333 |
with gr.Blocks() as app:
|
| 334 |
gr.Markdown("# YouTube Video Orchestrator Host")
|
| 335 |
gr.Markdown("Deploy this to Hugging Face Spaces or run locally. It fetches pending links from your database and delegates them to FastAPI workers or Kaggle.")
|
|
|
|
| 348 |
|
| 349 |
with gr.Tab("Kaggle Workers"):
|
| 350 |
gr.Markdown("Trigger Kaggle notebook workers. Note: You must mount `kaggle.json` to `~/.kaggle/kaggle.json` in the container for this to work.")
|
|
|
|
|
|
|
| 351 |
|
| 352 |
+
with gr.Row():
|
| 353 |
+
k_db_input = gr.Textbox(label="DB Path", value="/data/db/accounts.db")
|
| 354 |
+
k_links_per_worker = gr.Number(label="Links per worker", value=2)
|
| 355 |
+
k_max_workers = gr.Number(label="Max Workers", value=5)
|
| 356 |
+
|
| 357 |
+
k_run_btn = gr.Button("Run Kaggle Orchestration", variant="primary")
|
| 358 |
+
k_test_run_btn = gr.Button("Run Simple Kaggle Test Worker", variant="secondary")
|
| 359 |
+
kaggle_log = gr.Textbox(label="Kaggle Logs", lines=15)
|
| 360 |
+
|
| 361 |
+
k_run_btn.click(fn=trigger_kaggle_workers, inputs=[k_db_input, k_links_per_worker, k_max_workers], outputs=kaggle_log)
|
| 362 |
+
k_test_run_btn.click(fn=trigger_kaggle_test, inputs=[], outputs=kaggle_log)
|
| 363 |
|
| 364 |
if __name__ == "__main__":
|
| 365 |
app.launch(server_name="0.0.0.0", server_port=7860)
|