basyx commited on
Commit
aabf2cc
·
verified ·
1 Parent(s): 4f60cc3

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +165 -52
main.py CHANGED
@@ -1,14 +1,24 @@
1
  from fastapi import FastAPI, UploadFile, File, Form, Request
2
  from fastapi.responses import FileResponse, JSONResponse
 
3
  import os
4
  import uuid
5
  import asyncio
6
  import gradio as gr
7
 
 
 
 
8
  from utils.logger import logger
9
  from utils.job_queue import start_worker, create_job, get_job
10
  from ingestion.resolver import resolve_input
11
 
 
 
 
 
 
 
12
  # ==============================
13
  # CORE PIPELINE
14
  # ==============================
@@ -35,25 +45,39 @@ from publisher.metadata_engine import generate_metadata
35
  from publisher.thumbnail_engine import generate_thumbnail
36
 
37
  # ==============================
38
- # INIT
39
  # ==============================
40
- app = FastAPI(title="Basyx Whisper V10.1 Autonomous Operator")
41
-
42
  UPLOAD_DIR = "jobs"
43
  os.makedirs(UPLOAD_DIR, exist_ok=True)
44
 
45
- start_worker()
46
- init_scheduler()
47
 
 
 
 
 
 
 
 
 
 
48
 
49
- @app.on_event("startup")
50
- async def startup():
51
- logger.info("Starting Autonomous Publisher V10.1")
52
  asyncio.create_task(autonomous_loop())
53
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
  # ==============================
56
- # TASK REGISTRY (SAFE)
57
  # ==============================
58
  VALID_TASKS = {
59
  "autonomous",
@@ -70,7 +94,7 @@ VALID_TASKS = {
70
  "viral-score",
71
  "strategy",
72
  "batch",
73
- "clips", # ✅ FIXED missing alias
74
  }
75
 
76
 
@@ -82,9 +106,10 @@ def normalize_task(task: str):
82
 
83
 
84
  # ==============================
85
- # SAFE INPUT LOADER (CRITICAL FIX)
86
  # ==============================
87
  async def safe_resolve(file, source):
 
88
  try:
89
  if not file and not source:
90
  return None
@@ -92,7 +117,7 @@ async def safe_resolve(file, source):
92
  return await asyncio.to_thread(
93
  resolve_input,
94
  source,
95
- file
96
  )
97
 
98
  except Exception as e:
@@ -104,88 +129,152 @@ async def safe_resolve(file, source):
104
  # EXECUTION ENGINE
105
  # ==============================
106
  async def execute_task(video_path, task, payload=None, webhook=None):
 
107
  payload = payload or {}
108
  logger.info(f"[TASK] {task}")
109
 
110
- # ---------------- BULK ----------------
111
  if task == "bulk-publish":
112
  return await bulk_execute(payload), None
113
 
114
- # ---------------- INPUT GUARD ----------------
115
  if task not in ["bulk-publish", "schedule-post"] and not video_path:
116
  return {"error": "No valid input resolved"}, None
117
 
118
- # ---------------- AUTONOMOUS ----------------
119
  if task == "autonomous":
120
- result = await asyncio.to_thread(run_autonomous_engine, video_path)
 
 
121
  return result, None
122
 
123
  if task == "auto-publish":
124
- auto = await asyncio.to_thread(run_autonomous_engine, video_path)
125
- return await dispatch_publish(variants=auto.get("all_variants", [])), None
 
 
 
 
126
 
127
- # ---------------- PUBLISH ----------------
128
  if task == "publish":
129
- return await dispatch_publish(video_path=video_path, payload=payload), None
 
 
 
130
 
131
- # ---------------- METADATA ----------------
132
  if task == "generate-metadata":
133
  return generate_metadata(video_path), None
134
 
 
135
  if task == "generate-thumbnail":
136
- try:
137
- thumb = generate_thumbnail(video_path, output=f"{UPLOAD_DIR}/{uuid.uuid4()}.jpg")
138
- return {"thumbnail": thumb}, thumb
139
- except Exception as e:
140
- return {"error": str(e)}, None
141
 
142
- # ---------------- PIPELINE ----------------
 
 
 
 
 
 
 
 
 
 
 
 
143
  if task == "batch":
144
  job_id = create_job(video_path, webhook=webhook)
145
  return {"status": "queued", "job_id": job_id}, None
146
 
 
147
  if task == "transcribe":
148
- words = await asyncio.to_thread(transcribe_video, video_path)
 
 
 
149
  return {"words": words}, None
150
 
151
  if task == "subtitles":
152
- words = await asyncio.to_thread(transcribe_video, video_path)
 
 
 
153
  return {"srt": generate_srt(words)}, None
154
 
 
155
  if task == "render":
156
- try:
157
- words = await asyncio.to_thread(transcribe_video, video_path)
158
- srt = generate_srt(words)
159
 
160
- output = os.path.join(UPLOAD_DIR, f"{uuid.uuid4()}_render.mp4")
 
 
 
161
 
162
- await asyncio.to_thread(render_subtitles, video_path, srt, output)
163
 
164
- return {"status": "render_complete"}, output
 
 
 
 
 
 
 
 
 
 
165
 
166
- except Exception as e:
167
- return {"error": f"render_failed: {str(e)}"}, None
168
 
 
169
  if task == "highlights":
170
- words = await asyncio.to_thread(transcribe_video, video_path)
 
 
 
 
 
171
  highlights = detect_highlights(words) or []
172
 
173
  clips = create_clips(video_path, highlights)
174
 
175
- return {"clips_created": len(clips)}, (clips[0] if clips else None)
 
 
 
 
 
 
 
 
 
176
 
177
- if task == "clips": # ✅ FIXED handler
178
- words = await asyncio.to_thread(transcribe_video, video_path)
179
  highlights = detect_highlights(words) or []
180
- return {"clips": create_clips(video_path, highlights)}, None
181
 
 
 
 
 
 
182
  if task == "viral-score":
183
- words = await asyncio.to_thread(transcribe_video, video_path)
 
 
 
 
 
184
  segments = detect_highlights(words) or []
185
- return {"scores": [score_clip(s) for s in segments]}, None
186
 
 
 
 
 
 
187
  if task == "strategy":
188
- words = await asyncio.to_thread(transcribe_video, video_path)
 
 
 
 
189
 
190
  script = rewrite_script(words)
191
  persona = predict_audience(words)
@@ -205,7 +294,7 @@ async def execute_task(video_path, task, payload=None, webhook=None):
205
 
206
 
207
  # ==============================
208
- # ROUTER
209
  # ==============================
210
  @app.post("/execute/{task_name}")
211
  async def execute_router(
@@ -216,25 +305,41 @@ async def execute_router(
216
  source: str = Form(None),
217
  webhook: str = Form(None),
218
  ):
 
219
  try:
 
220
  task = normalize_task(task_name)
221
 
222
  payload = {}
223
- if request.headers.get("content-type", "").startswith("application/json"):
 
 
 
224
  payload = await request.json()
225
 
226
- video_path = await safe_resolve(file, url_input or source)
 
 
 
227
 
228
- result, output = await execute_task(video_path, task, payload, webhook)
 
 
 
 
 
229
 
230
- if output and isinstance(output, str) and os.path.exists(output):
231
  return FileResponse(output)
232
 
233
  return {"task": task, "result": result}
234
 
235
  except Exception as e:
236
  logger.exception(e)
237
- return JSONResponse({"error": str(e)}, status_code=500)
 
 
 
238
 
239
 
240
  # ==============================
@@ -254,15 +359,23 @@ def status(job_id: str):
254
  # GRADIO UI
255
  # ==============================
256
  async def ui_handler(video, task, webhook, url_input):
 
257
  source = url_input or video
 
258
  video_path = await safe_resolve(video, source)
259
 
260
- result, output = await execute_task(video_path, normalize_task(task), {}, webhook)
 
 
 
 
 
261
 
262
  return str(result), output
263
 
264
 
265
  with gr.Blocks() as demo:
 
266
  gr.Markdown("# 🚀 Basyx Whisper V10.1 Stable Operator")
267
 
268
  video_input = gr.Video()
 
1
  from fastapi import FastAPI, UploadFile, File, Form, Request
2
  from fastapi.responses import FileResponse, JSONResponse
3
+ from contextlib import asynccontextmanager
4
  import os
5
  import uuid
6
  import asyncio
7
  import gradio as gr
8
 
9
+ # ==============================
10
+ # LOGGER + QUEUE
11
+ # ==============================
12
  from utils.logger import logger
13
  from utils.job_queue import start_worker, create_job, get_job
14
  from ingestion.resolver import resolve_input
15
 
16
+ # ==============================
17
+ # AUTH SYSTEM ✅ NEW
18
+ # ==============================
19
+ from auth.routes import router as auth_router
20
+ from auth.database import Base, engine
21
+
22
  # ==============================
23
  # CORE PIPELINE
24
  # ==============================
 
45
  from publisher.thumbnail_engine import generate_thumbnail
46
 
47
  # ==============================
48
+ # INIT (LIFESPAN SAFE)
49
  # ==============================
 
 
50
  UPLOAD_DIR = "jobs"
51
  os.makedirs(UPLOAD_DIR, exist_ok=True)
52
 
 
 
53
 
54
+ @asynccontextmanager
55
+ async def lifespan(app: FastAPI):
56
+
57
+ logger.info("Starting Basyx Whisper V10.1")
58
+
59
+ Base.metadata.create_all(bind=engine)
60
+
61
+ start_worker()
62
+ init_scheduler()
63
 
 
 
 
64
  asyncio.create_task(autonomous_loop())
65
 
66
+ yield
67
+
68
+ logger.info("Shutdown complete")
69
+
70
+
71
+ app = FastAPI(
72
+ title="Basyx Whisper V10.1 Autonomous Operator",
73
+ lifespan=lifespan,
74
+ )
75
+
76
+ # Register Auth Routes
77
+ app.include_router(auth_router)
78
 
79
  # ==============================
80
+ # TASK REGISTRY
81
  # ==============================
82
  VALID_TASKS = {
83
  "autonomous",
 
94
  "viral-score",
95
  "strategy",
96
  "batch",
97
+ "clips",
98
  }
99
 
100
 
 
106
 
107
 
108
  # ==============================
109
+ # SAFE INPUT RESOLVER
110
  # ==============================
111
  async def safe_resolve(file, source):
112
+
113
  try:
114
  if not file and not source:
115
  return None
 
117
  return await asyncio.to_thread(
118
  resolve_input,
119
  source,
120
+ file,
121
  )
122
 
123
  except Exception as e:
 
129
  # EXECUTION ENGINE
130
  # ==============================
131
  async def execute_task(video_path, task, payload=None, webhook=None):
132
+
133
  payload = payload or {}
134
  logger.info(f"[TASK] {task}")
135
 
136
+ # -------- BULK ----------
137
  if task == "bulk-publish":
138
  return await bulk_execute(payload), None
139
 
 
140
  if task not in ["bulk-publish", "schedule-post"] and not video_path:
141
  return {"error": "No valid input resolved"}, None
142
 
143
+ # -------- AUTONOMOUS ----------
144
  if task == "autonomous":
145
+ result = await asyncio.to_thread(
146
+ run_autonomous_engine, video_path
147
+ )
148
  return result, None
149
 
150
  if task == "auto-publish":
151
+ auto = await asyncio.to_thread(
152
+ run_autonomous_engine, video_path
153
+ )
154
+ return await dispatch_publish(
155
+ variants=auto.get("all_variants", [])
156
+ ), None
157
 
158
+ # -------- PUBLISH ----------
159
  if task == "publish":
160
+ return await dispatch_publish(
161
+ video_path=video_path,
162
+ payload=payload,
163
+ ), None
164
 
165
+ # -------- METADATA ----------
166
  if task == "generate-metadata":
167
  return generate_metadata(video_path), None
168
 
169
+ # ✅ THUMBNAIL FIX
170
  if task == "generate-thumbnail":
 
 
 
 
 
171
 
172
+ output_path = os.path.join(
173
+ UPLOAD_DIR,
174
+ f"{uuid.uuid4()}.jpg"
175
+ )
176
+
177
+ thumb = generate_thumbnail(
178
+ video_path,
179
+ output=output_path,
180
+ )
181
+
182
+ return {"thumbnail": thumb}, output_path
183
+
184
+ # -------- QUEUE ----------
185
  if task == "batch":
186
  job_id = create_job(video_path, webhook=webhook)
187
  return {"status": "queued", "job_id": job_id}, None
188
 
189
+ # -------- TRANSCRIBE ----------
190
  if task == "transcribe":
191
+ words = await asyncio.to_thread(
192
+ transcribe_video,
193
+ video_path,
194
+ )
195
  return {"words": words}, None
196
 
197
  if task == "subtitles":
198
+ words = await asyncio.to_thread(
199
+ transcribe_video,
200
+ video_path,
201
+ )
202
  return {"srt": generate_srt(words)}, None
203
 
204
+ # -------- RENDER ----------
205
  if task == "render":
 
 
 
206
 
207
+ words = await asyncio.to_thread(
208
+ transcribe_video,
209
+ video_path,
210
+ )
211
 
212
+ srt = generate_srt(words)
213
 
214
+ output = os.path.join(
215
+ UPLOAD_DIR,
216
+ f"{uuid.uuid4()}_render.mp4",
217
+ )
218
+
219
+ await asyncio.to_thread(
220
+ render_subtitles,
221
+ video_path,
222
+ srt,
223
+ output,
224
+ )
225
 
226
+ return {"status": "render_complete"}, output
 
227
 
228
+ # -------- HIGHLIGHTS ----------
229
  if task == "highlights":
230
+
231
+ words = await asyncio.to_thread(
232
+ transcribe_video,
233
+ video_path,
234
+ )
235
+
236
  highlights = detect_highlights(words) or []
237
 
238
  clips = create_clips(video_path, highlights)
239
 
240
+ return {
241
+ "clips_created": len(clips)
242
+ }, (clips[0] if clips else None)
243
+
244
+ if task == "clips":
245
+
246
+ words = await asyncio.to_thread(
247
+ transcribe_video,
248
+ video_path,
249
+ )
250
 
 
 
251
  highlights = detect_highlights(words) or []
 
252
 
253
+ return {
254
+ "clips": create_clips(video_path, highlights)
255
+ }, None
256
+
257
+ # -------- SCORING ----------
258
  if task == "viral-score":
259
+
260
+ words = await asyncio.to_thread(
261
+ transcribe_video,
262
+ video_path,
263
+ )
264
+
265
  segments = detect_highlights(words) or []
 
266
 
267
+ return {
268
+ "scores": [score_clip(s) for s in segments]
269
+ }, None
270
+
271
+ # -------- STRATEGY ----------
272
  if task == "strategy":
273
+
274
+ words = await asyncio.to_thread(
275
+ transcribe_video,
276
+ video_path,
277
+ )
278
 
279
  script = rewrite_script(words)
280
  persona = predict_audience(words)
 
294
 
295
 
296
  # ==============================
297
+ # EXECUTE ROUTER
298
  # ==============================
299
  @app.post("/execute/{task_name}")
300
  async def execute_router(
 
305
  source: str = Form(None),
306
  webhook: str = Form(None),
307
  ):
308
+
309
  try:
310
+
311
  task = normalize_task(task_name)
312
 
313
  payload = {}
314
+
315
+ if request.headers.get(
316
+ "content-type", ""
317
+ ).startswith("application/json"):
318
  payload = await request.json()
319
 
320
+ video_path = await safe_resolve(
321
+ file,
322
+ url_input or source,
323
+ )
324
 
325
+ result, output = await execute_task(
326
+ video_path,
327
+ task,
328
+ payload,
329
+ webhook,
330
+ )
331
 
332
+ if output and os.path.exists(output):
333
  return FileResponse(output)
334
 
335
  return {"task": task, "result": result}
336
 
337
  except Exception as e:
338
  logger.exception(e)
339
+ return JSONResponse(
340
+ {"error": str(e)},
341
+ status_code=500,
342
+ )
343
 
344
 
345
  # ==============================
 
359
  # GRADIO UI
360
  # ==============================
361
  async def ui_handler(video, task, webhook, url_input):
362
+
363
  source = url_input or video
364
+
365
  video_path = await safe_resolve(video, source)
366
 
367
+ result, output = await execute_task(
368
+ video_path,
369
+ normalize_task(task),
370
+ {},
371
+ webhook,
372
+ )
373
 
374
  return str(result), output
375
 
376
 
377
  with gr.Blocks() as demo:
378
+
379
  gr.Markdown("# 🚀 Basyx Whisper V10.1 Stable Operator")
380
 
381
  video_input = gr.Video()