Fred808 commited on
Commit
3178657
·
verified ·
1 Parent(s): 3dbbc64

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +66 -221
app.py CHANGED
@@ -73,242 +73,87 @@ async def root():
73
  }
74
  }
75
 
76
- @app.post("/upload/")
77
- async def upload_file(file: UploadFile = File(...)):
78
- """Uploads a file and stores it by filename inside the upload folder"""
79
- log_request("upload", {"filename": file.filename})
80
-
81
- if not file.filename.lower().endswith('.mp4'):
82
- raise HTTPException(status_code=400, detail="Only MP4 files are allowed")
83
-
84
- save_path = os.path.join(MP4_UPLOAD_FOLDER, file.filename)
85
- save_file(file, save_path)
86
-
87
- logger.info(f"File uploaded successfully: {file.filename}")
88
- return {"status": "uploaded", "filename": file.filename, "size": os.path.getsize(save_path)}
89
 
90
- @app.get("/list")
91
- async def list_uploaded_files():
92
- """List uploaded MP4 files"""
93
- log_request("list")
94
 
95
- files = []
96
- if os.path.exists(MP4_UPLOAD_FOLDER):
97
- for file in os.listdir(MP4_UPLOAD_FOLDER):
98
- if file.lower().endswith(".mp4"):
99
- file_path = os.path.join(MP4_UPLOAD_FOLDER, file)
100
- files.append({
101
- "filename": file,
102
- "size": os.path.getsize(file_path),
103
- "modified": os.path.getmtime(file_path)
104
- })
105
 
106
- logger.info(f"Listed {len(files)} uploaded files")
107
- return JSONResponse(content=files)
 
 
 
108
 
109
  @app.get("/download")
110
- async def download_file(course: str = Query(..., description="Course folder name"),
111
- file: str = Query(..., description="MP4 filename")):
112
- """
113
- Download any MP4 under processed_mp4s, including nested course folders.
114
- Usage: GET /download?course=MDS_CGAdventure_DownloadPirate.com&file=1.mp4
115
- """
116
- log_request("download", {"course": course, "file": file})
117
-
118
- logger.info(f"[DOWNLOAD] Request for course: {course}, file: {file}")
119
 
120
- # Validate inputs
121
- if not course or not file:
122
- raise HTTPException(status_code=400, detail="Both course and file parameters are required")
123
 
124
- if not file.lower().endswith('.mp4'):
125
- raise HTTPException(status_code=400, detail="File must be an MP4")
126
-
127
- # Security: Clean the inputs
128
- course = course.strip().replace('..', '').replace('\\', '/')
129
- file = file.strip().replace('..', '').replace('\\', '/')
130
-
131
- # Prevent path traversal in individual components
132
- if '/' in file or '\\' in file:
133
- raise HTTPException(status_code=400, detail="File parameter cannot contain path separators")
134
-
135
- # Construct the path
136
- mp4_path = f"{course}/{file}"
137
- full_path = os.path.join(MP4_OUTPUT_FOLDER, course, file)
138
-
139
- logger.info(f"[DOWNLOAD] Constructed path: {mp4_path}")
140
- logger.info(f"[DOWNLOAD] Full system path: {full_path}")
141
-
142
- # Security: Prevent directory traversal
143
- full_path = os.path.normpath(full_path)
144
- base_path = os.path.abspath(MP4_OUTPUT_FOLDER)
145
-
146
- if not full_path.startswith(base_path):
147
- logger.warning(f"[SECURITY] Path traversal attempt - course: {course}, file: {file}")
148
- raise HTTPException(status_code=400, detail="Invalid path - security violation")
149
-
150
- logger.info(f"[DOWNLOAD] Normalized path: {full_path}")
151
- logger.info(f"[DOWNLOAD] File exists: {os.path.exists(full_path)}")
152
-
153
- if not os.path.exists(full_path):
154
- logger.warning(f"[DOWNLOAD] File not found: {full_path}")
155
-
156
- # Debug: Show what's actually in the course folder
157
- course_path = os.path.join(MP4_OUTPUT_FOLDER, course)
158
- logger.info(f"[DOWNLOAD] Checking course folder: {course_path}")
159
-
160
- if os.path.exists(course_path):
161
- logger.info(f"[DOWNLOAD] Course folder exists, contents:")
162
- try:
163
- for item in os.listdir(course_path):
164
- item_path = os.path.join(course_path, item)
165
- if os.path.isfile(item_path):
166
- logger.info(f"[DOWNLOAD] File: {item} ({os.path.getsize(item_path)} bytes)")
167
- else:
168
- logger.info(f"[DOWNLOAD] Directory: {item}")
169
- except Exception as e:
170
- logger.error(f"[DOWNLOAD] Error listing course contents: {e}")
171
- else:
172
- logger.info(f"[DOWNLOAD] Course folder does not exist")
173
-
174
- # Show available courses
175
- logger.info("[DOWNLOAD] Available courses:")
176
- if os.path.exists(MP4_OUTPUT_FOLDER):
177
- for item in os.listdir(MP4_OUTPUT_FOLDER):
178
- item_path = os.path.join(MP4_OUTPUT_FOLDER, item)
179
- if os.path.isdir(item_path):
180
- logger.info(f"[DOWNLOAD] Course: {item}")
181
-
182
- raise HTTPException(
183
- status_code=404,
184
- detail=f"File not found: {file} in course {course}"
185
- )
186
 
187
- file_size = os.path.getsize(full_path)
188
- logger.info(f"[DOWNLOAD] Serving file: {full_path} ({file_size} bytes)")
 
 
189
 
190
- return FileResponse(
191
- path=full_path,
192
- filename=file,
193
- media_type="video/mp4",
194
- headers={
195
- "Content-Length": str(file_size),
196
- "Accept-Ranges": "bytes"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  }
198
- )
199
 
200
- @app.get("/courses")
201
- async def list_courses():
202
- """List all unique course folder names based on extracted MP4 paths."""
203
- log_request("courses")
204
-
205
- course_folders = set()
206
-
207
- logger.info(f"[COURSES] Scanning {MP4_OUTPUT_FOLDER} for courses")
208
 
209
- if os.path.exists(MP4_OUTPUT_FOLDER):
210
- for root, dirs, files in os.walk(MP4_OUTPUT_FOLDER):
211
- for file in files:
212
- if file.lower().endswith(".mp4"):
213
- rel_path = os.path.relpath(os.path.join(root, file), MP4_OUTPUT_FOLDER)
214
- course_folder = os.path.dirname(rel_path)
215
- if course_folder and course_folder != ".":
216
- course_folders.add(course_folder)
217
- logger.info(f"[COURSES] Found course: {course_folder} (from file: {rel_path})")
218
-
219
- courses_list = sorted(course_folders)
220
- logger.info(f"[COURSES] Returning {len(courses_list)} courses: {courses_list}")
221
- return JSONResponse(content={"courses": courses_list, "total": len(courses_list)})
222
-
223
- @app.get("/images/{course_folder:path}")
224
- async def list_mp4s_in_course(course_folder: str):
225
- """List MP4s available for a specific course folder"""
226
- log_request("images", {"course_folder": course_folder})
227
-
228
- logger.info(f"[IMAGES] Listing MP4s for course: {course_folder}")
229
-
230
- course_path = os.path.join(MP4_OUTPUT_FOLDER, course_folder)
231
- logger.info(f"[IMAGES] Course path: {course_path}")
232
- logger.info(f"[IMAGES] Path exists: {os.path.exists(course_path)}")
233
-
234
- if not os.path.exists(course_path):
235
- logger.warning(f"[IMAGES] Course folder not found: {course_folder}")
236
-
237
- # List what's actually available
238
- logger.info("[IMAGES] Available course folders:")
239
- if os.path.exists(MP4_OUTPUT_FOLDER):
240
- for item in os.listdir(MP4_OUTPUT_FOLDER):
241
- item_path = os.path.join(MP4_OUTPUT_FOLDER, item)
242
- if os.path.isdir(item_path):
243
- logger.info(f"[IMAGES] {item}")
244
-
245
- raise HTTPException(status_code=404, detail=f"Course folder not found: {course_folder}")
246
-
247
- mp4s = []
248
- try:
249
- for file in os.listdir(course_path):
250
  if file.lower().endswith(".mp4"):
251
- mp4s.append(file)
252
- file_path = os.path.join(course_path, file)
253
- file_size = os.path.getsize(file_path)
254
- logger.info(f"[IMAGES] Found MP4: {file} ({file_size} bytes)")
255
- except Exception as e:
256
- logger.error(f"[IMAGES] Failed to list files in {course_path}: {e}")
257
- raise HTTPException(status_code=500, detail="Failed to list files")
258
-
259
- logger.info(f"[IMAGES] Returning {len(mp4s)} MP4s: {mp4s}")
260
- return JSONResponse(content=mp4s)
261
 
262
- @app.get("/debug/structure")
263
- async def debug_file_structure():
264
- """Debug endpoint to show the actual file structure"""
265
- log_request("debug/structure")
266
-
267
- structure = {}
268
- total_files = 0
269
- total_size = 0
270
-
271
- if os.path.exists(MP4_OUTPUT_FOLDER):
272
- for root, dirs, files in os.walk(MP4_OUTPUT_FOLDER):
273
- rel_root = os.path.relpath(root, MP4_OUTPUT_FOLDER)
274
- if rel_root == ".":
275
- rel_root = "/"
276
-
277
- mp4_files = []
278
- other_files = []
279
-
280
- for file in files:
281
- file_path = os.path.join(root, file)
282
- file_size = os.path.getsize(file_path)
283
- total_size += file_size
284
-
285
- file_info = {
286
- "name": file,
287
- "size": file_size,
288
- "modified": os.path.getmtime(file_path)
289
- }
290
-
291
- if file.lower().endswith('.mp4'):
292
- mp4_files.append(file_info)
293
- total_files += 1
294
- else:
295
- other_files.append(file_info)
296
-
297
- structure[rel_root] = {
298
- "directories": dirs,
299
- "mp4_files": mp4_files,
300
- "other_files": other_files
301
- }
302
-
303
- logger.info(f"[DEBUG] File structure scan complete - {total_files} MP4 files, {total_size} bytes total")
304
-
305
- return JSONResponse(content={
306
- "mp4_output_folder": MP4_OUTPUT_FOLDER,
307
- "folder_exists": os.path.exists(MP4_OUTPUT_FOLDER),
308
- "total_mp4_files": total_files,
309
- "total_size_bytes": total_size,
310
  "structure": structure
311
- })
312
 
313
  @app.on_event("startup")
314
  async def startup_event():
 
73
  }
74
  }
75
 
76
+ @app.get("/courses")
77
+ async def get_courses():
78
+ """List all top-level course folders."""
79
+ try:
80
+ courses = [d.name for d in Path(MP4_OUTPUT_FOLDER).iterdir() if d.is_dir()]
81
+ return {"courses": courses, "total": len(courses)}
82
+ except Exception as e:
83
+ raise HTTPException(status_code=500, detail=f"Failed to list courses: {e}")
 
 
 
 
 
84
 
85
+ @app.get("/images/{course_folder:path}")
86
+ async def get_mp4_list(course_folder: str):
87
+ """List all MP4 files within a specific course folder."""
88
+ course_path = Path(MP4_OUTPUT_FOLDER) / course_folder
89
 
90
+ if not course_path.is_dir():
91
+ raise HTTPException(status_code=404, detail="Course folder not found")
 
 
 
 
 
 
 
 
92
 
93
+ try:
94
+ mp4_files = [f.name for f in course_path.iterdir() if f.is_file() and f.suffix.lower() == ".mp4"]
95
+ return mp4_files
96
+ except Exception as e:
97
+ raise HTTPException(status_code=500, detail=f"Failed to list MP4s: {e}")
98
 
99
  @app.get("/download")
100
+ async def download_mp4(course: str, file: str):
101
+ """Download a specific MP4 file from a course folder."""
102
+ file_path = Path(MP4_OUTPUT_FOLDER) / course / file
 
 
 
 
 
 
103
 
104
+ if not file_path.is_file():
105
+ raise HTTPException(status_code=404, detail="File not found")
 
106
 
107
+ return FileResponse(path=file_path, media_type="video/mp4", filename=file)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
+ @app.get("/debug/structure")
110
+ async def debug_structure():
111
+ """Debug endpoint to inspect the file structure and sizes."""
112
+ mp4_output_folder_path = Path(MP4_OUTPUT_FOLDER)
113
 
114
+ structure = {}
115
+ total_size_bytes = 0
116
+ total_mp4_files = 0
117
+
118
+ if not mp4_output_folder_path.exists():
119
+ return JSONResponse(content={
120
+ "mp4_output_folder": str(mp4_output_folder_path),
121
+ "folder_exists": False,
122
+ "total_mp4_files": 0,
123
+ "total_size_bytes": 0,
124
+ "structure": {}
125
+ })
126
+
127
+ for root, dirs, files in os.walk(mp4_output_folder_path):
128
+ current_path = Path(root)
129
+ relative_path = str(current_path.relative_to(mp4_output_folder_path))
130
+ if relative_path == ".":
131
+ relative_path = "/"
132
+
133
+ structure[relative_path] = {
134
+ "directories": [d for d in dirs],
135
+ "mp4_files": [],
136
+ "other_files": []
137
  }
 
138
 
139
+ for file in files:
140
+ file_full_path = current_path / file
141
+ file_size = file_full_path.stat().st_size
142
+ total_size_bytes += file_size
 
 
 
 
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  if file.lower().endswith(".mp4"):
145
+ structure[relative_path]["mp4_files"].append({"name": file, "size": file_size})
146
+ total_mp4_files += 1
147
+ else:
148
+ structure[relative_path]["other_files"].append({"name": file, "size": file_size})
 
 
 
 
 
 
149
 
150
+ return {
151
+ "mp4_output_folder": str(mp4_output_folder_path),
152
+ "folder_exists": mp4_output_folder_path.exists(),
153
+ "total_mp4_files": total_mp4_files,
154
+ "total_size_bytes": total_size_bytes,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  "structure": structure
156
+ }
157
 
158
  @app.on_event("startup")
159
  async def startup_event():