VeuReu commited on
Commit
82a94f5
·
verified ·
1 Parent(s): a6a4697

Upload 3 files

Browse files
Files changed (3) hide show
  1. storage/common.py +13 -0
  2. storage/db_routers.py +95 -0
  3. storage/media_routers.py +272 -363
storage/common.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import HTTPException
2
+
3
+ def validate_token(token: str):
4
+ """
5
+ Validate the provided token against the HF_TOKEN environment variable.
6
+ Raises an HTTPException if validation fails.
7
+ """
8
+ HF_TOKEN = os.getenv("HF_TOKEN")
9
+ if HF_TOKEN is None:
10
+ raise RuntimeError("HF_TOKEN environment variable is not set on the server.")
11
+
12
+ if token != HF_TOKEN:
13
+ raise HTTPException(status_code=401, detail="Invalid token")
storage/db_routers.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ import sqlite3
4
+
5
+ from pathlib import Path
6
+
7
+ from fastapi import APIRouter, UploadFile, File, Query, HTTPException
8
+ from fastapi.responses import JSONResponse
9
+
10
+ from storage.common import validate_token
11
+
12
+ router = APIRouter(prefix="/db", tags=["DB Manager"])
13
+ HF_TOKEN = os.getenv("HF_TOKEN")
14
+ DB_PATH = Path("/data/db")
15
+
16
+ @router.post("/upload_db_file", tags=["Database Manager"])
17
+ async def upload_db_file(
18
+ file: UploadFile = File(...),
19
+ token: str = Query(..., description="Token required for authorization")
20
+ ):
21
+ """
22
+ Upload a file to the /data/db folder.
23
+
24
+ Steps:
25
+ - Validate the token.
26
+ - Ensure /data/db exists (create it if missing).
27
+ - Save the uploaded file inside /data/db using its original filename.
28
+ - Return a JSON response confirming the upload.
29
+ """
30
+ validate_token(token)
31
+
32
+ # Crear carpeta /data/db si no existe
33
+ DB_PATH.mkdir(parents=True, exist_ok=True)
34
+
35
+ final_path = DB_PATH / file.filename
36
+
37
+ try:
38
+ # Leer contenido en memoria y guardar
39
+ file_bytes = await file.read()
40
+ with open(final_path, "wb") as f:
41
+ f.write(file_bytes)
42
+ except Exception as exc:
43
+ raise HTTPException(status_code=500, detail=f"Failed to save file: {exc}")
44
+
45
+ return JSONResponse(
46
+ status_code=200,
47
+ content={
48
+ "status": "ok",
49
+ "saved_to": str(final_path)
50
+ }
51
+ )
52
+
53
+ @router.post("/execute_query", tags=["Database Manager"])
54
+ async def execute_query(
55
+ db_filename: str = Query(..., description="SQLite .db file name"),
56
+ query: str = Query(..., description="SQL query to execute"),
57
+ token: str = Query(..., description="Token required for authorization")
58
+ ):
59
+ """
60
+ Execute a SQL query against a specified SQLite database file in /data/db.
61
+
62
+ Steps:
63
+ - Validate the token.
64
+ - Ensure the requested .db file exists inside /data/db.
65
+ - Connect to the database using sqlite3.
66
+ - Execute the provided query.
67
+ - Return the results as a list of dictionaries (column: value).
68
+ - Capture and return errors if query execution fails.
69
+ """
70
+ validate_token(token)
71
+
72
+ db_file_path = DB_PATH / db_filename
73
+
74
+ if not db_file_path.exists() or not db_file_path.is_file():
75
+ raise HTTPException(status_code=404, detail=f"Database file {db_filename} not found")
76
+
77
+ try:
78
+ conn = sqlite3.connect(db_file_path)
79
+ conn.row_factory = sqlite3.Row # para devolver columnas por nombre
80
+ cur = conn.cursor()
81
+
82
+ cur.execute(query)
83
+ rows = cur.fetchall()
84
+
85
+ # Convert rows to list of dicts
86
+ results = [dict(row) for row in rows]
87
+
88
+ conn.commit()
89
+ conn.close()
90
+ except sqlite3.Error as e:
91
+ raise HTTPException(status_code=400, detail=f"SQL error: {e}")
92
+ except Exception as e:
93
+ raise HTTPException(status_code=500, detail=f"Unexpected error: {e}")
94
+
95
+ return JSONResponse(content={"results": results})
storage/media_routers.py CHANGED
@@ -1,363 +1,272 @@
1
- import os
2
- import io
3
- import shutil
4
-
5
- import sqlite3
6
-
7
- from pathlib import Path
8
-
9
- from fastapi import APIRouter, UploadFile, File, Query, HTTPException
10
- from fastapi.responses import FileResponse, JSONResponse
11
-
12
-
13
- from storage.files.file_manager import FileManager
14
-
15
- router = APIRouter(prefix="/media", tags=["Media Manager"])
16
- MEDIA_ROOT = Path("/data/media")
17
- file_manager = FileManager(MEDIA_ROOT)
18
- HF_TOKEN = os.getenv("HF_TOKEN")
19
- DB_PATH = Path("/data/db")
20
-
21
- def validate_token(token: str):
22
- """
23
- Validate the provided token against the HF_TOKEN environment variable.
24
- Raises an HTTPException if validation fails.
25
- """
26
- if HF_TOKEN is None:
27
- raise RuntimeError("HF_TOKEN environment variable is not set on the server.")
28
-
29
- if token != HF_TOKEN:
30
- raise HTTPException(status_code=401, detail="Invalid token")
31
-
32
- @router.post("/upload_db_file", tags=["Database Manager"])
33
- async def upload_db_file(
34
- file: UploadFile = File(...),
35
- token: str = Query(..., description="Token required for authorization")
36
- ):
37
- """
38
- Upload a file to the /data/db folder.
39
-
40
- Steps:
41
- - Validate the token.
42
- - Ensure /data/db exists (create it if missing).
43
- - Save the uploaded file inside /data/db using its original filename.
44
- - Return a JSON response confirming the upload.
45
- """
46
- validate_token(token)
47
-
48
- # Crear carpeta /data/db si no existe
49
- DB_PATH.mkdir(parents=True, exist_ok=True)
50
-
51
- final_path = DB_PATH / file.filename
52
-
53
- try:
54
- # Leer contenido en memoria y guardar
55
- file_bytes = await file.read()
56
- with open(final_path, "wb") as f:
57
- f.write(file_bytes)
58
- except Exception as exc:
59
- raise HTTPException(status_code=500, detail=f"Failed to save file: {exc}")
60
-
61
- return JSONResponse(
62
- status_code=200,
63
- content={
64
- "status": "ok",
65
- "saved_to": str(final_path)
66
- }
67
- )
68
-
69
- @router.post("/execute_query", tags=["Database Manager"])
70
- async def execute_query(
71
- db_filename: str = Query(..., description="SQLite .db file name"),
72
- query: str = Query(..., description="SQL query to execute"),
73
- token: str = Query(..., description="Token required for authorization")
74
- ):
75
- """
76
- Execute a SQL query against a specified SQLite database file in /data/db.
77
-
78
- Steps:
79
- - Validate the token.
80
- - Ensure the requested .db file exists inside /data/db.
81
- - Connect to the database using sqlite3.
82
- - Execute the provided query.
83
- - Return the results as a list of dictionaries (column: value).
84
- - Capture and return errors if query execution fails.
85
- """
86
- validate_token(token)
87
-
88
- db_file_path = DB_PATH / db_filename
89
-
90
- if not db_file_path.exists() or not db_file_path.is_file():
91
- raise HTTPException(status_code=404, detail=f"Database file {db_filename} not found")
92
-
93
- try:
94
- conn = sqlite3.connect(db_file_path)
95
- conn.row_factory = sqlite3.Row # para devolver columnas por nombre
96
- cur = conn.cursor()
97
-
98
- cur.execute(query)
99
- rows = cur.fetchall()
100
-
101
- # Convert rows to list of dicts
102
- results = [dict(row) for row in rows]
103
-
104
- conn.commit()
105
- conn.close()
106
- except sqlite3.Error as e:
107
- raise HTTPException(status_code=400, detail=f"SQL error: {e}")
108
- except Exception as e:
109
- raise HTTPException(status_code=500, detail=f"Unexpected error: {e}")
110
-
111
- return JSONResponse(content={"results": results})
112
-
113
- @router.delete("/clear_media", tags=["Media Manager"])
114
- def clear_media(token: str = Query(..., description="Token required for authorization")):
115
- """
116
- Delete all contents of the /data/media folder.
117
-
118
- Steps:
119
- - Validate the token.
120
- - Ensure the folder exists.
121
- - Delete all files and subfolders inside /data/media.
122
- - Return a JSON response confirming the deletion.
123
-
124
- Warning: This will remove all stored videos, clips, and cast CSV files.
125
- """
126
- validate_token(token)
127
-
128
- if not MEDIA_ROOT.exists() or not MEDIA_ROOT.is_dir():
129
- raise HTTPException(status_code=404, detail="/data/media folder does not exist")
130
-
131
- # Delete contents
132
- for item in MEDIA_ROOT.iterdir():
133
- try:
134
- if item.is_dir():
135
- shutil.rmtree(item)
136
- else:
137
- item.unlink()
138
- except Exception as e:
139
- raise HTTPException(status_code=500, detail=f"Failed to delete {item}: {e}")
140
-
141
- return {"status": "ok", "message": "All media files deleted successfully"}
142
-
143
- @router.post("/upload_cast_csv/{sha1}", tags=["Media Manager"])
144
- async def upload_cast_csv(
145
- sha1: str,
146
- cast_file: UploadFile = File(...),
147
- token: str = Query(..., description="Token required for authorization")
148
- ):
149
- """
150
- Upload a cast CSV file for a specific video identified by its SHA-1.
151
-
152
- The CSV will be stored under:
153
- /data/media/<sha1>/cast/cast.csv
154
-
155
- Steps:
156
- - Validate the token.
157
- - Ensure /data/media/<sha1> exists.
158
- - Create /cast folder if missing.
159
- - Save the CSV file inside /cast.
160
- """
161
- validate_token(token)
162
-
163
- base_folder = MEDIA_ROOT / sha1
164
- if not base_folder.exists() or not base_folder.is_dir():
165
- raise HTTPException(status_code=404, detail="SHA1 folder not found")
166
-
167
- cast_folder = base_folder / "cast"
168
- cast_folder.mkdir(parents=True, exist_ok=True)
169
-
170
- final_path = cast_folder / "cast.csv"
171
-
172
- file_bytes = await cast_file.read()
173
- save_result = file_manager.upload_file(io.BytesIO(file_bytes), final_path)
174
- if not save_result["operation_success"]:
175
- raise HTTPException(status_code=500, detail=save_result["error"])
176
-
177
- return JSONResponse(
178
- status_code=200,
179
- content={"status": "ok", "saved_to": str(final_path)}
180
- )
181
-
182
-
183
- @router.get("/download_cast_csv/{sha1}", tags=["Media Manager"])
184
- def download_cast_csv(
185
- sha1: str,
186
- token: str = Query(..., description="Token required for authorization")
187
- ):
188
- """
189
- Download the cast CSV for a specific video identified by its SHA-1.
190
-
191
- The CSV is expected under:
192
- /data/media/<sha1>/cast/cast.csv
193
-
194
- Steps:
195
- - Validate the token.
196
- - Ensure /data/media/<sha1> and /cast exist.
197
- - Return the CSV as a FileResponse.
198
- - Raise 404 if any folder or file is missing.
199
- """
200
- MEDIA_ROOT = Path("/data/media")
201
- file_manager = FileManager(MEDIA_ROOT)
202
- HF_TOKEN = os.getenv("HF_TOKEN")
203
- validate_token(token)
204
-
205
- base_folder = MEDIA_ROOT / sha1
206
- cast_folder = base_folder / "cast"
207
- csv_path = cast_folder / "cast.csv"
208
-
209
- if not base_folder.exists() or not base_folder.is_dir():
210
- raise HTTPException(status_code=404, detail="SHA1 folder not found")
211
- if not cast_folder.exists() or not cast_folder.is_dir():
212
- raise HTTPException(status_code=404, detail="Cast folder not found")
213
- if not csv_path.exists() or not csv_path.is_file():
214
- raise HTTPException(status_code=404, detail="Cast CSV not found")
215
-
216
- # Convert to relative path for FileManager
217
- relative_path = csv_path.relative_to(MEDIA_ROOT)
218
- handler = file_manager.get_file(relative_path)
219
- if handler is None:
220
- raise HTTPException(status_code=404, detail="Cast CSV not accessible")
221
- handler.close()
222
-
223
- return FileResponse(
224
- path=csv_path,
225
- media_type="text/csv",
226
- filename="cast.csv"
227
- )
228
-
229
- @router.post("/upload_original_video", tags=["Media Manager"])
230
- async def upload_video(
231
- video: UploadFile = File(...),
232
- token: str = Query(..., description="Token required for authorization")
233
- ):
234
- """
235
- Saves an uploaded video by hashing it with SHA1 and placing it under:
236
- /data/media/<sha1>/clip/<original_filename>
237
-
238
- Steps:
239
- - Compute SHA1 of the uploaded video.
240
- - Ensure /data/media exists.
241
- - Create folder /data/media/<sha1> if missing.
242
- - Create folder /data/media/<sha1>/clip if missing.
243
- - Save the video inside /data/media/<sha1>/clip/.
244
- """
245
- MEDIA_ROOT = Path("/data/media")
246
- file_manager = FileManager(MEDIA_ROOT)
247
- HF_TOKEN = os.getenv("HF_TOKEN")
248
- validate_token(token)
249
-
250
- # Read content into memory (needed to compute hash twice)
251
- file_bytes = await video.read()
252
-
253
- # Create an in-memory file handler
254
- file_handler = io.BytesIO(file_bytes)
255
-
256
- # Compute SHA1 using your FileManager method
257
- try:
258
- sha1 = file_manager.compute_sha1(file_handler)
259
- except Exception as exc:
260
- raise HTTPException(status_code=500, detail=f"SHA1 computation failed: {exc}")
261
-
262
- # Ensure /data/media exists
263
- MEDIA_ROOT.mkdir(parents=True, exist_ok=True)
264
-
265
- # Path: /data/media/<sha1>
266
- video_root = MEDIA_ROOT / sha1
267
- video_root.mkdir(parents=True, exist_ok=True)
268
-
269
- # Path: /data/media/<sha1>/clip
270
- clip_dir = video_root / "clip"
271
- clip_dir.mkdir(parents=True, exist_ok=True)
272
-
273
- # Final file path
274
- final_path = clip_dir / video.filename
275
-
276
- # Save file using your FileManager.upload_file
277
- save_result = file_manager.upload_file(io.BytesIO(file_bytes), final_path)
278
-
279
- if not save_result["operation_success"]:
280
- raise HTTPException(status_code=500, detail=save_result["error"])
281
-
282
- return JSONResponse(
283
- status_code=200,
284
- content={
285
- "status": "ok",
286
- "sha1": sha1,
287
- "saved_to": str(final_path)
288
- }
289
- )
290
-
291
-
292
- @router.get("/download_original_video/{sha1}", tags=["Media Manager"])
293
- def download_video(
294
- sha1: str,
295
- token: str = Query(..., description="Token required for authorization")
296
- ):
297
- """
298
- Download a stored video by its SHA-1 directory name.
299
-
300
- This endpoint looks for a video stored under the path:
301
- /data/media/<sha1>/clip/
302
- and returns the first MP4 file found in that folder.
303
-
304
- The method performs the following steps:
305
- - Checks if the SHA-1 folder exists inside the media root.
306
- - Validates that the "clip" subfolder exists.
307
- - Searches for the first .mp4 file inside the clip folder.
308
- - Uses the FileManager.get_file method to ensure the file is accessible.
309
- - Returns the video directly as a FileResponse.
310
-
311
- Parameters
312
- ----------
313
- sha1 : str
314
- The SHA-1 hash corresponding to the directory where the video is stored.
315
-
316
- Returns
317
- -------
318
- FileResponse
319
- A streaming response containing the MP4 video.
320
-
321
- Raises
322
- ------
323
- HTTPException
324
- - 404 if the SHA-1 folder does not exist.
325
- - 404 if the clip folder is missing.
326
- - 404 if no MP4 files are found.
327
- - 404 if the file cannot be retrieved using FileManager.
328
- """
329
- MEDIA_ROOT = Path("/data/media")
330
- file_manager = FileManager(MEDIA_ROOT)
331
- HF_TOKEN = os.getenv("HF_TOKEN")
332
- validate_token(token)
333
-
334
- sha1_folder = MEDIA_ROOT / sha1
335
- clip_folder = sha1_folder / "clip"
336
-
337
- if not sha1_folder.exists() or not sha1_folder.is_dir():
338
- raise HTTPException(status_code=404, detail="SHA1 folder not found")
339
-
340
- if not clip_folder.exists() or not clip_folder.is_dir():
341
- raise HTTPException(status_code=404, detail="Clip folder not found")
342
-
343
- # Find first MP4 file
344
- mp4_files = list(clip_folder.glob("*.mp4"))
345
- if not mp4_files:
346
- raise HTTPException(status_code=404, detail="No MP4 files found")
347
-
348
- video_path = mp4_files[0]
349
-
350
- # Convert to relative path for FileManager
351
- relative_path = video_path.relative_to(MEDIA_ROOT)
352
-
353
- handler = file_manager.get_file(relative_path)
354
- if handler is None:
355
- raise HTTPException(status_code=404, detail="Video not accessible")
356
-
357
- handler.close()
358
-
359
- return FileResponse(
360
- path=video_path,
361
- media_type="video/mp4",
362
- filename=video_path.name
363
- )
 
1
+ import os
2
+ import io
3
+ import shutil
4
+
5
+ import sqlite3
6
+
7
+ from pathlib import Path
8
+
9
+ from fastapi import APIRouter, UploadFile, File, Query, HTTPException
10
+ from fastapi.responses import FileResponse, JSONResponse
11
+
12
+
13
+ from storage.files.file_manager import FileManager
14
+ from storage.common import validate_token
15
+
16
+ router = APIRouter(prefix="/media", tags=["Media Manager"])
17
+ MEDIA_ROOT = Path("/data/media")
18
+ file_manager = FileManager(MEDIA_ROOT)
19
+ HF_TOKEN = os.getenv("HF_TOKEN")
20
+
21
+
22
+ @router.delete("/clear_media", tags=["Media Manager"])
23
+ def clear_media(token: str = Query(..., description="Token required for authorization")):
24
+ """
25
+ Delete all contents of the /data/media folder.
26
+
27
+ Steps:
28
+ - Validate the token.
29
+ - Ensure the folder exists.
30
+ - Delete all files and subfolders inside /data/media.
31
+ - Return a JSON response confirming the deletion.
32
+
33
+ Warning: This will remove all stored videos, clips, and cast CSV files.
34
+ """
35
+ validate_token(token)
36
+
37
+ if not MEDIA_ROOT.exists() or not MEDIA_ROOT.is_dir():
38
+ raise HTTPException(status_code=404, detail="/data/media folder does not exist")
39
+
40
+ # Delete contents
41
+ for item in MEDIA_ROOT.iterdir():
42
+ try:
43
+ if item.is_dir():
44
+ shutil.rmtree(item)
45
+ else:
46
+ item.unlink()
47
+ except Exception as e:
48
+ raise HTTPException(status_code=500, detail=f"Failed to delete {item}: {e}")
49
+
50
+ return {"status": "ok", "message": "All media files deleted successfully"}
51
+
52
+ @router.post("/upload_cast_csv/{sha1}", tags=["Media Manager"])
53
+ async def upload_cast_csv(
54
+ sha1: str,
55
+ cast_file: UploadFile = File(...),
56
+ token: str = Query(..., description="Token required for authorization")
57
+ ):
58
+ """
59
+ Upload a cast CSV file for a specific video identified by its SHA-1.
60
+
61
+ The CSV will be stored under:
62
+ /data/media/<sha1>/cast/cast.csv
63
+
64
+ Steps:
65
+ - Validate the token.
66
+ - Ensure /data/media/<sha1> exists.
67
+ - Create /cast folder if missing.
68
+ - Save the CSV file inside /cast.
69
+ """
70
+ validate_token(token)
71
+
72
+ base_folder = MEDIA_ROOT / sha1
73
+ if not base_folder.exists() or not base_folder.is_dir():
74
+ raise HTTPException(status_code=404, detail="SHA1 folder not found")
75
+
76
+ cast_folder = base_folder / "cast"
77
+ cast_folder.mkdir(parents=True, exist_ok=True)
78
+
79
+ final_path = cast_folder / "cast.csv"
80
+
81
+ file_bytes = await cast_file.read()
82
+ save_result = file_manager.upload_file(io.BytesIO(file_bytes), final_path)
83
+ if not save_result["operation_success"]:
84
+ raise HTTPException(status_code=500, detail=save_result["error"])
85
+
86
+ return JSONResponse(
87
+ status_code=200,
88
+ content={"status": "ok", "saved_to": str(final_path)}
89
+ )
90
+
91
+
92
+ @router.get("/download_cast_csv/{sha1}", tags=["Media Manager"])
93
+ def download_cast_csv(
94
+ sha1: str,
95
+ token: str = Query(..., description="Token required for authorization")
96
+ ):
97
+ """
98
+ Download the cast CSV for a specific video identified by its SHA-1.
99
+
100
+ The CSV is expected under:
101
+ /data/media/<sha1>/cast/cast.csv
102
+
103
+ Steps:
104
+ - Validate the token.
105
+ - Ensure /data/media/<sha1> and /cast exist.
106
+ - Return the CSV as a FileResponse.
107
+ - Raise 404 if any folder or file is missing.
108
+ """
109
+ MEDIA_ROOT = Path("/data/media")
110
+ file_manager = FileManager(MEDIA_ROOT)
111
+ HF_TOKEN = os.getenv("HF_TOKEN")
112
+ validate_token(token)
113
+
114
+ base_folder = MEDIA_ROOT / sha1
115
+ cast_folder = base_folder / "cast"
116
+ csv_path = cast_folder / "cast.csv"
117
+
118
+ if not base_folder.exists() or not base_folder.is_dir():
119
+ raise HTTPException(status_code=404, detail="SHA1 folder not found")
120
+ if not cast_folder.exists() or not cast_folder.is_dir():
121
+ raise HTTPException(status_code=404, detail="Cast folder not found")
122
+ if not csv_path.exists() or not csv_path.is_file():
123
+ raise HTTPException(status_code=404, detail="Cast CSV not found")
124
+
125
+ # Convert to relative path for FileManager
126
+ relative_path = csv_path.relative_to(MEDIA_ROOT)
127
+ handler = file_manager.get_file(relative_path)
128
+ if handler is None:
129
+ raise HTTPException(status_code=404, detail="Cast CSV not accessible")
130
+ handler.close()
131
+
132
+ return FileResponse(
133
+ path=csv_path,
134
+ media_type="text/csv",
135
+ filename="cast.csv"
136
+ )
137
+
138
+ @router.post("/upload_original_video", tags=["Media Manager"])
139
+ async def upload_video(
140
+ video: UploadFile = File(...),
141
+ token: str = Query(..., description="Token required for authorization")
142
+ ):
143
+ """
144
+ Saves an uploaded video by hashing it with SHA1 and placing it under:
145
+ /data/media/<sha1>/clip/<original_filename>
146
+
147
+ Steps:
148
+ - Compute SHA1 of the uploaded video.
149
+ - Ensure /data/media exists.
150
+ - Create folder /data/media/<sha1> if missing.
151
+ - Create folder /data/media/<sha1>/clip if missing.
152
+ - Save the video inside /data/media/<sha1>/clip/.
153
+ """
154
+ MEDIA_ROOT = Path("/data/media")
155
+ file_manager = FileManager(MEDIA_ROOT)
156
+ HF_TOKEN = os.getenv("HF_TOKEN")
157
+ validate_token(token)
158
+
159
+ # Read content into memory (needed to compute hash twice)
160
+ file_bytes = await video.read()
161
+
162
+ # Create an in-memory file handler
163
+ file_handler = io.BytesIO(file_bytes)
164
+
165
+ # Compute SHA1 using your FileManager method
166
+ try:
167
+ sha1 = file_manager.compute_sha1(file_handler)
168
+ except Exception as exc:
169
+ raise HTTPException(status_code=500, detail=f"SHA1 computation failed: {exc}")
170
+
171
+ # Ensure /data/media exists
172
+ MEDIA_ROOT.mkdir(parents=True, exist_ok=True)
173
+
174
+ # Path: /data/media/<sha1>
175
+ video_root = MEDIA_ROOT / sha1
176
+ video_root.mkdir(parents=True, exist_ok=True)
177
+
178
+ # Path: /data/media/<sha1>/clip
179
+ clip_dir = video_root / "clip"
180
+ clip_dir.mkdir(parents=True, exist_ok=True)
181
+
182
+ # Final file path
183
+ final_path = clip_dir / video.filename
184
+
185
+ # Save file using your FileManager.upload_file
186
+ save_result = file_manager.upload_file(io.BytesIO(file_bytes), final_path)
187
+
188
+ if not save_result["operation_success"]:
189
+ raise HTTPException(status_code=500, detail=save_result["error"])
190
+
191
+ return JSONResponse(
192
+ status_code=200,
193
+ content={
194
+ "status": "ok",
195
+ "sha1": sha1,
196
+ "saved_to": str(final_path)
197
+ }
198
+ )
199
+
200
+
201
+ @router.get("/download_original_video/{sha1}", tags=["Media Manager"])
202
+ def download_video(
203
+ sha1: str,
204
+ token: str = Query(..., description="Token required for authorization")
205
+ ):
206
+ """
207
+ Download a stored video by its SHA-1 directory name.
208
+
209
+ This endpoint looks for a video stored under the path:
210
+ /data/media/<sha1>/clip/
211
+ and returns the first MP4 file found in that folder.
212
+
213
+ The method performs the following steps:
214
+ - Checks if the SHA-1 folder exists inside the media root.
215
+ - Validates that the "clip" subfolder exists.
216
+ - Searches for the first .mp4 file inside the clip folder.
217
+ - Uses the FileManager.get_file method to ensure the file is accessible.
218
+ - Returns the video directly as a FileResponse.
219
+
220
+ Parameters
221
+ ----------
222
+ sha1 : str
223
+ The SHA-1 hash corresponding to the directory where the video is stored.
224
+
225
+ Returns
226
+ -------
227
+ FileResponse
228
+ A streaming response containing the MP4 video.
229
+
230
+ Raises
231
+ ------
232
+ HTTPException
233
+ - 404 if the SHA-1 folder does not exist.
234
+ - 404 if the clip folder is missing.
235
+ - 404 if no MP4 files are found.
236
+ - 404 if the file cannot be retrieved using FileManager.
237
+ """
238
+ MEDIA_ROOT = Path("/data/media")
239
+ file_manager = FileManager(MEDIA_ROOT)
240
+ HF_TOKEN = os.getenv("HF_TOKEN")
241
+ validate_token(token)
242
+
243
+ sha1_folder = MEDIA_ROOT / sha1
244
+ clip_folder = sha1_folder / "clip"
245
+
246
+ if not sha1_folder.exists() or not sha1_folder.is_dir():
247
+ raise HTTPException(status_code=404, detail="SHA1 folder not found")
248
+
249
+ if not clip_folder.exists() or not clip_folder.is_dir():
250
+ raise HTTPException(status_code=404, detail="Clip folder not found")
251
+
252
+ # Find first MP4 file
253
+ mp4_files = list(clip_folder.glob("*.mp4"))
254
+ if not mp4_files:
255
+ raise HTTPException(status_code=404, detail="No MP4 files found")
256
+
257
+ video_path = mp4_files[0]
258
+
259
+ # Convert to relative path for FileManager
260
+ relative_path = video_path.relative_to(MEDIA_ROOT)
261
+
262
+ handler = file_manager.get_file(relative_path)
263
+ if handler is None:
264
+ raise HTTPException(status_code=404, detail="Video not accessible")
265
+
266
+ handler.close()
267
+
268
+ return FileResponse(
269
+ path=video_path,
270
+ media_type="video/mp4",
271
+ filename=video_path.name
272
+ )