Prithwis commited on
Commit
be81231
·
verified ·
1 Parent(s): c977dd1

Force update backend\fastapi_app.py - 1757357472

Browse files
Files changed (1) hide show
  1. backend/fastapi_app.py +226 -0
backend/fastapi_app.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ FastAPI backend with WebSocket support for real-time video processing
3
+ """
4
+
5
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect, UploadFile, File, HTTPException, BackgroundTasks
6
+ from fastapi.responses import FileResponse, StreamingResponse
7
+ from fastapi.middleware.cors import CORSMiddleware
8
+ from fastapi.staticfiles import StaticFiles
9
+ import uvicorn
10
+ import os
11
+ import uuid
12
+ import json
13
+ import asyncio
14
+ from pathlib import Path
15
+ from typing import Dict, Optional
16
+ import tempfile
17
+ import shutil
18
+
19
+ from async_processor import async_processor
20
+
21
+ app = FastAPI(title="VR180 Converter API", version="2.0.0")
22
+
23
+ # CORS middleware
24
+ app.add_middleware(
25
+ CORSMiddleware,
26
+ allow_origins=["*"],
27
+ allow_credentials=True,
28
+ allow_methods=["*"],
29
+ allow_headers=["*"],
30
+ )
31
+
32
+ # Create necessary directories
33
+ os.makedirs("uploads", exist_ok=True)
34
+ os.makedirs("outputs", exist_ok=True)
35
+ os.makedirs("thumbnails", exist_ok=True)
36
+ os.makedirs("static", exist_ok=True)
37
+
38
+ # Mount static files
39
+ app.mount("/static", StaticFiles(directory="static"), name="static")
40
+
41
+ @app.get("/")
42
+ async def root():
43
+ """Health check endpoint"""
44
+ return {"message": "VR180 Converter API is running", "version": "2.0.0"}
45
+
46
+ @app.get("/api/health")
47
+ async def health_check():
48
+ """Detailed health check"""
49
+ return {
50
+ "status": "healthy",
51
+ "message": "VR180 Converter API is running",
52
+ "version": "2.0.0",
53
+ "active_jobs": len(async_processor.processing_jobs),
54
+ "active_connections": len(async_processor.active_connections)
55
+ }
56
+
57
+ @app.post("/api/upload")
58
+ async def upload_video(file: UploadFile = File(...)):
59
+ """Upload video file and return metadata"""
60
+ try:
61
+ # Validate file type
62
+ allowed_extensions = {'.mp4', '.avi', '.mov', '.mkv', '.webm'}
63
+ file_extension = Path(file.filename).suffix.lower()
64
+
65
+ if file_extension not in allowed_extensions:
66
+ raise HTTPException(status_code=400, detail="Invalid file type")
67
+
68
+ # Generate unique filename
69
+ file_id = str(uuid.uuid4())
70
+ filename = f"{file_id}_{file.filename}"
71
+ filepath = os.path.join("uploads", filename)
72
+
73
+ # Save file
74
+ with open(filepath, "wb") as buffer:
75
+ shutil.copyfileobj(file.file, buffer)
76
+
77
+ # Get video info
78
+ video_info = async_processor.get_video_info(filepath)
79
+
80
+ # Generate thumbnail
81
+ thumbnail_path = async_processor.generate_thumbnail(filepath)
82
+
83
+ return {
84
+ "success": True,
85
+ "file_id": file_id,
86
+ "filename": filename,
87
+ "original_name": file.filename,
88
+ "video_info": video_info,
89
+ "thumbnail": thumbnail_path,
90
+ "file_size": os.path.getsize(filepath)
91
+ }
92
+
93
+ except Exception as e:
94
+ raise HTTPException(status_code=500, detail=str(e))
95
+
96
+ @app.post("/api/process/{file_id}")
97
+ async def start_processing(file_id: str, background_tasks: BackgroundTasks):
98
+ """Start video processing job"""
99
+ try:
100
+ # Find the uploaded file
101
+ upload_dir = Path("uploads")
102
+ files = list(upload_dir.glob(f"{file_id}_*"))
103
+
104
+ if not files:
105
+ raise HTTPException(status_code=404, detail="File not found")
106
+
107
+ input_path = str(files[0])
108
+
109
+ # Generate output path
110
+ output_filename = f"vr180_{Path(input_path).name}"
111
+ output_path = os.path.join("outputs", output_filename)
112
+
113
+ # Start processing in background
114
+ job_id = str(uuid.uuid4())
115
+ background_tasks.add_task(
116
+ async_processor.process_video_async,
117
+ input_path,
118
+ output_path,
119
+ job_id
120
+ )
121
+
122
+ return {
123
+ "success": True,
124
+ "job_id": job_id,
125
+ "message": "Processing started",
126
+ "websocket_url": f"/ws/{job_id}"
127
+ }
128
+
129
+ except Exception as e:
130
+ raise HTTPException(status_code=500, detail=str(e))
131
+
132
+ @app.websocket("/ws/{job_id}")
133
+ async def websocket_endpoint(websocket: WebSocket, job_id: str):
134
+ """WebSocket endpoint for real-time updates"""
135
+ await async_processor.connect(websocket, job_id)
136
+
137
+ try:
138
+ while True:
139
+ # Keep connection alive
140
+ data = await websocket.receive_text()
141
+ message = json.loads(data)
142
+
143
+ if message.get("type") == "ping":
144
+ await websocket.send_text(json.dumps({"type": "pong"}))
145
+
146
+ except WebSocketDisconnect:
147
+ async_processor.disconnect(job_id)
148
+
149
+ @app.get("/api/status/{job_id}")
150
+ async def get_job_status(job_id: str):
151
+ """Get processing job status"""
152
+ status = async_processor.get_job_status(job_id)
153
+
154
+ if not status:
155
+ raise HTTPException(status_code=404, detail="Job not found")
156
+
157
+ return status
158
+
159
+ @app.get("/api/download/{filename}")
160
+ async def download_video(filename: str):
161
+ """Download processed video"""
162
+ filepath = os.path.join("outputs", filename)
163
+
164
+ if not os.path.exists(filepath):
165
+ raise HTTPException(status_code=404, detail="File not found")
166
+
167
+ return FileResponse(
168
+ filepath,
169
+ media_type="video/mp4",
170
+ filename=filename,
171
+ headers={"Content-Disposition": f"attachment; filename={filename}"}
172
+ )
173
+
174
+ @app.get("/api/thumbnail/{filename}")
175
+ async def get_thumbnail(filename: str):
176
+ """Get video thumbnail"""
177
+ thumb_path = os.path.join("thumbnails", filename)
178
+
179
+ if not os.path.exists(thumb_path):
180
+ raise HTTPException(status_code=404, detail="Thumbnail not found")
181
+
182
+ return FileResponse(thumb_path, media_type="image/jpeg")
183
+
184
+ @app.delete("/api/cleanup/{file_id}")
185
+ async def cleanup_files(file_id: str):
186
+ """Clean up uploaded and processed files"""
187
+ try:
188
+ # Remove uploaded file
189
+ upload_dir = Path("uploads")
190
+ upload_files = list(upload_dir.glob(f"{file_id}_*"))
191
+ for file in upload_files:
192
+ file.unlink()
193
+
194
+ # Remove output file
195
+ output_dir = Path("outputs")
196
+ output_files = list(output_dir.glob(f"vr180_{file_id}_*"))
197
+ for file in output_files:
198
+ file.unlink()
199
+
200
+ # Remove thumbnail
201
+ thumb_dir = Path("thumbnails")
202
+ thumb_files = list(thumb_dir.glob(f"{file_id}_*"))
203
+ for file in thumb_files:
204
+ file.unlink()
205
+
206
+ return {"success": True, "message": "Files cleaned up"}
207
+
208
+ except Exception as e:
209
+ raise HTTPException(status_code=500, detail=str(e))
210
+
211
+ @app.get("/api/jobs")
212
+ async def list_jobs():
213
+ """List all processing jobs"""
214
+ return {
215
+ "jobs": async_processor.processing_jobs,
216
+ "active_connections": len(async_processor.active_connections)
217
+ }
218
+
219
+ if __name__ == "__main__":
220
+ uvicorn.run(
221
+ "fastapi_app:app",
222
+ host="0.0.0.0",
223
+ port=8000,
224
+ reload=True,
225
+ workers=1 # Single worker for shared state
226
+ )