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

Upload 4 files

Browse files
storage/db_routers.py CHANGED
@@ -9,7 +9,7 @@ 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
 
 
9
 
10
  from storage.common import validate_token
11
 
12
+ router = APIRouter(prefix="/db", tags=["Database Manager"])
13
  HF_TOKEN = os.getenv("HF_TOKEN")
14
  DB_PATH = Path("/data/db")
15
 
storage/files/file_manager.py CHANGED
@@ -1,203 +1,203 @@
1
- """
2
- file_manager.py
3
-
4
- This module provides the FileManager class, a high-level interface for managing
5
- files and directories inside a specified media folder. It centralizes common
6
- operations such as reading, writing, listing, copying, moving, and deleting files,
7
- ensuring that all actions are safely constrained within the defined root directory.
8
- The class is designed to simplify file handling logic in larger applications by
9
- offering a consistent, validated, and extensible API for filesystem interactions.
10
- """
11
-
12
- from pathlib import Path
13
-
14
- class FileManager:
15
- """
16
- FileManager is a utility class that encapsulates common filesystem operations
17
- within a defined media directory. It ensures that all file manipulations remain
18
- inside the configured root folder and provides helper methods to interact with
19
- files and subdirectories in a structured, safe, and predictable manner.
20
-
21
- Typical use cases include managing uploaded media, performing batch operations
22
- on directory contents, and abstracting filesystem complexity behind a clean API.
23
- """
24
-
25
- def __init__(self, media_folder: Path):
26
- """
27
- Initialize the FileManager with a specific root directory for all file
28
- operations.
29
-
30
- Parameters
31
- ----------
32
- media_folder : Path
33
- The base directory where all file and folder operations will be performed.
34
- It must be a valid filesystem path. If the directory does not exist, the
35
- instance will attempt to create it automatically.
36
-
37
- Raises
38
- ------
39
- ValueError
40
- If the provided media_folder path is not a valid directory or cannot be
41
- created.
42
- """
43
- self.media_folder = Path(media_folder)
44
-
45
- if not self.media_folder.exists():
46
- try:
47
- self.media_folder.mkdir(parents=True)
48
- except Exception as exc:
49
- raise ValueError(
50
- f"Unable to create media folder at: {self.media_folder}"
51
- ) from exc
52
-
53
- if not self.media_folder.is_dir():
54
- raise ValueError(f"Media folder is not a directory: {self.media_folder}")
55
-
56
-
57
- def upload_file(self, file_handler, destination: Path):
58
- """
59
- Upload a file to a target destination inside the media folder.
60
-
61
- This method takes a file-like object and writes its contents to the
62
- specified destination within the media folder. The method ensures the path
63
- remains inside the media folder and creates necessary directories. It
64
- returns a structured response indicating whether the operation succeeded
65
- or failed, along with any relevant error message.
66
-
67
- Parameters
68
- ----------
69
- file_handler : file-like object
70
- A file-like object opened in binary mode that provides a `.read()` method.
71
- destination : Path
72
- The relative or absolute path (within the media folder) where the file
73
- should be saved.
74
-
75
- Returns
76
- -------
77
- dict
78
- A dictionary with:
79
- - "operation_success" (bool): True if the file was saved successfully.
80
- - "error" (str): An empty string on success, or the error message on failure.
81
-
82
- Raises
83
- ------
84
- None
85
- Any exceptions are captured and returned inside the result dictionary.
86
- """
87
- try:
88
- destination = self.media_folder / destination
89
- destination = destination.resolve()
90
-
91
- # Ensure the destination lies inside the media folder
92
- if self.media_folder not in destination.parents:
93
- return {
94
- "operation_success": False,
95
- "error": "Destination path is outside the media folder."
96
- }
97
-
98
- # Create parent directories if needed
99
- destination.parent.mkdir(parents=True, exist_ok=True)
100
-
101
- with open(destination, "wb") as f:
102
- f.write(file_handler.read())
103
-
104
- return {"operation_success": True, "error": ""}
105
-
106
- except Exception as exc:
107
- return {
108
- "operation_success": False,
109
- "error": str(exc)
110
- }
111
-
112
- def get_file(self, file_path: Path):
113
- """
114
- Retrieve a file inside the media folder and return an open file handler.
115
-
116
- This method receives a path pointing to a file expected to be located
117
- inside the media folder. If the file exists and is valid, the method
118
- returns a file handler opened in binary read mode. If the file does not
119
- exist or the resolved path escapes the media folder, the method returns
120
- None.
121
-
122
- Parameters
123
- ----------
124
- file_path : Path
125
- The relative or absolute path to the file within the media folder.
126
-
127
- Returns
128
- -------
129
- file object or None
130
- A file handler opened in 'rb' mode if the file exists and is accessible.
131
- Returns None if the file does not exist or the path is invalid.
132
-
133
- Raises
134
- ------
135
- None
136
- All exceptions are handled internally and result in returning None.
137
- """
138
- try:
139
- target = self.media_folder / file_path
140
- target = target.resolve()
141
-
142
- # Ensure the resolved path is inside the media folder
143
- if self.media_folder not in target.parents:
144
- return None
145
-
146
- if not target.exists() or not target.is_file():
147
- return None
148
-
149
- return open(target, "rb")
150
-
151
- except Exception:
152
- return None
153
-
154
- def compute_sha1(self, file_handler):
155
- """
156
- Compute the SHA-1 checksum of a file handler.
157
-
158
- This method calculates the SHA-1 hash of the content provided by a
159
- file-like object opened in binary mode. The method preserves the
160
- current file pointer position by saving it, rewinding to the start
161
- of the file, computing the hash, and restoring the file pointer to
162
- its original position.
163
-
164
- Parameters
165
- ----------
166
- file_handler : file-like object
167
- A file-like object opened in binary mode, providing `.read()` and
168
- `.seek()` methods.
169
-
170
- Returns
171
- -------
172
- str
173
- The hexadecimal SHA-1 checksum of the file content.
174
-
175
- Raises
176
- ------
177
- ValueError
178
- If the provided file handler is invalid or does not support the
179
- required read/seek operations.
180
- """
181
- import hashlib
182
-
183
- if not hasattr(file_handler, "read") or not hasattr(file_handler, "seek"):
184
- raise ValueError("The provided file handler does not support read/seek operations.")
185
-
186
- original_pos = file_handler.tell()
187
-
188
- try:
189
- file_handler.seek(0)
190
- sha1 = hashlib.sha1()
191
-
192
- for chunk in iter(lambda: file_handler.read(8192), b""):
193
- sha1.update(chunk)
194
-
195
- file_handler.seek(original_pos)
196
- return sha1.hexdigest()
197
-
198
- except Exception as exc:
199
- try:
200
- file_handler.seek(original_pos)
201
- except Exception:
202
- pass
203
- raise ValueError(f"Unable to compute SHA-1 checksum: {exc}") from exc
 
1
+ """
2
+ file_manager.py
3
+
4
+ This module provides the FileManager class, a high-level interface for managing
5
+ files and directories inside a specified media folder. It centralizes common
6
+ operations such as reading, writing, listing, copying, moving, and deleting files,
7
+ ensuring that all actions are safely constrained within the defined root directory.
8
+ The class is designed to simplify file handling logic in larger applications by
9
+ offering a consistent, validated, and extensible API for filesystem interactions.
10
+ """
11
+
12
+ from pathlib import Path
13
+
14
+ class FileManager:
15
+ """
16
+ FileManager is a utility class that encapsulates common filesystem operations
17
+ within a defined media directory. It ensures that all file manipulations remain
18
+ inside the configured root folder and provides helper methods to interact with
19
+ files and subdirectories in a structured, safe, and predictable manner.
20
+
21
+ Typical use cases include managing uploaded media, performing batch operations
22
+ on directory contents, and abstracting filesystem complexity behind a clean API.
23
+ """
24
+
25
+ def __init__(self, media_folder: Path):
26
+ """
27
+ Initialize the FileManager with a specific root directory for all file
28
+ operations.
29
+
30
+ Parameters
31
+ ----------
32
+ media_folder : Path
33
+ The base directory where all file and folder operations will be performed.
34
+ It must be a valid filesystem path. If the directory does not exist, the
35
+ instance will attempt to create it automatically.
36
+
37
+ Raises
38
+ ------
39
+ ValueError
40
+ If the provided media_folder path is not a valid directory or cannot be
41
+ created.
42
+ """
43
+ self.media_folder = Path(media_folder)
44
+
45
+ if not self.media_folder.exists():
46
+ try:
47
+ self.media_folder.mkdir(parents=True)
48
+ except Exception as exc:
49
+ raise ValueError(
50
+ f"Unable to create media folder at: {self.media_folder}"
51
+ ) from exc
52
+
53
+ if not self.media_folder.is_dir():
54
+ raise ValueError(f"Media folder is not a directory: {self.media_folder}")
55
+
56
+
57
+ def upload_file(self, file_handler, destination: Path):
58
+ """
59
+ Upload a file to a target destination inside the media folder.
60
+
61
+ This method takes a file-like object and writes its contents to the
62
+ specified destination within the media folder. The method ensures the path
63
+ remains inside the media folder and creates necessary directories. It
64
+ returns a structured response indicating whether the operation succeeded
65
+ or failed, along with any relevant error message.
66
+
67
+ Parameters
68
+ ----------
69
+ file_handler : file-like object
70
+ A file-like object opened in binary mode that provides a `.read()` method.
71
+ destination : Path
72
+ The relative or absolute path (within the media folder) where the file
73
+ should be saved.
74
+
75
+ Returns
76
+ -------
77
+ dict
78
+ A dictionary with:
79
+ - "operation_success" (bool): True if the file was saved successfully.
80
+ - "error" (str): An empty string on success, or the error message on failure.
81
+
82
+ Raises
83
+ ------
84
+ None
85
+ Any exceptions are captured and returned inside the result dictionary.
86
+ """
87
+ try:
88
+ destination = self.media_folder / destination
89
+ destination = destination.resolve()
90
+
91
+ # Ensure the destination lies inside the media folder
92
+ if self.media_folder not in destination.parents:
93
+ return {
94
+ "operation_success": False,
95
+ "error": "Destination path is outside the media folder."
96
+ }
97
+
98
+ # Create parent directories if needed
99
+ destination.parent.mkdir(parents=True, exist_ok=True)
100
+
101
+ with open(destination, "wb") as f:
102
+ f.write(file_handler.read())
103
+
104
+ return {"operation_success": True, "error": ""}
105
+
106
+ except Exception as exc:
107
+ return {
108
+ "operation_success": False,
109
+ "error": str(exc)
110
+ }
111
+
112
+ def get_file(self, file_path: Path):
113
+ """
114
+ Retrieve a file inside the media folder and return an open file handler.
115
+
116
+ This method receives a path pointing to a file expected to be located
117
+ inside the media folder. If the file exists and is valid, the method
118
+ returns a file handler opened in binary read mode. If the file does not
119
+ exist or the resolved path escapes the media folder, the method returns
120
+ None.
121
+
122
+ Parameters
123
+ ----------
124
+ file_path : Path
125
+ The relative or absolute path to the file within the media folder.
126
+
127
+ Returns
128
+ -------
129
+ file object or None
130
+ A file handler opened in 'rb' mode if the file exists and is accessible.
131
+ Returns None if the file does not exist or the path is invalid.
132
+
133
+ Raises
134
+ ------
135
+ None
136
+ All exceptions are handled internally and result in returning None.
137
+ """
138
+ try:
139
+ target = self.media_folder / file_path
140
+ target = target.resolve()
141
+
142
+ # Ensure the resolved path is inside the media folder
143
+ if self.media_folder not in target.parents:
144
+ return None
145
+
146
+ if not target.exists() or not target.is_file():
147
+ return None
148
+
149
+ return open(target, "rb")
150
+
151
+ except Exception:
152
+ return None
153
+
154
+ def compute_sha1(self, file_handler):
155
+ """
156
+ Compute the SHA-1 checksum of a file handler.
157
+
158
+ This method calculates the SHA-1 hash of the content provided by a
159
+ file-like object opened in binary mode. The method preserves the
160
+ current file pointer position by saving it, rewinding to the start
161
+ of the file, computing the hash, and restoring the file pointer to
162
+ its original position.
163
+
164
+ Parameters
165
+ ----------
166
+ file_handler : file-like object
167
+ A file-like object opened in binary mode, providing `.read()` and
168
+ `.seek()` methods.
169
+
170
+ Returns
171
+ -------
172
+ str
173
+ The hexadecimal SHA-1 checksum of the file content.
174
+
175
+ Raises
176
+ ------
177
+ ValueError
178
+ If the provided file handler is invalid or does not support the
179
+ required read/seek operations.
180
+ """
181
+ import hashlib
182
+
183
+ if not hasattr(file_handler, "read") or not hasattr(file_handler, "seek"):
184
+ raise ValueError("The provided file handler does not support read/seek operations.")
185
+
186
+ original_pos = file_handler.tell()
187
+
188
+ try:
189
+ file_handler.seek(0)
190
+ sha1 = hashlib.sha1()
191
+
192
+ for chunk in iter(lambda: file_handler.read(8192), b""):
193
+ sha1.update(chunk)
194
+
195
+ file_handler.seek(original_pos)
196
+ return sha1.hexdigest()
197
+
198
+ except Exception as exc:
199
+ try:
200
+ file_handler.seek(original_pos)
201
+ except Exception:
202
+ pass
203
+ raise ValueError(f"Unable to compute SHA-1 checksum: {exc}") from exc
storage/media_routers.py CHANGED
@@ -270,3 +270,55 @@ def download_video(
270
  media_type="video/mp4",
271
  filename=video_path.name
272
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  media_type="video/mp4",
271
  filename=video_path.name
272
  )
273
+
274
+ @router.get("/list_original_videos", tags=["Media Manager"])
275
+ def list_all_videos(
276
+ token: str = Query(..., description="Token required for authorization")
277
+ ):
278
+ """
279
+ List all videos stored under /data/media.
280
+
281
+ For each SHA1 folder, the endpoint returns:
282
+ - sha1: folder name
283
+ - video_files: list of mp4 files inside /clip
284
+ - latest_video: the most recently modified mp4
285
+ - video_count: total number of mp4 files
286
+
287
+ Notes:
288
+ - Videos may not have a /clip folder.
289
+ - SHA1 folders without mp4 files are still returned.
290
+ """
291
+ validate_token(token)
292
+
293
+ results = []
294
+
295
+ # If media root does not exist, return empty list
296
+ if not MEDIA_ROOT.exists():
297
+ return []
298
+
299
+ for sha1_dir in MEDIA_ROOT.iterdir():
300
+ if not sha1_dir.is_dir():
301
+ continue # skip non-folders
302
+
303
+ clip_dir = sha1_dir / "clip"
304
+
305
+ videos = []
306
+ latest_video = None
307
+
308
+ if clip_dir.exists() and clip_dir.is_dir():
309
+ mp4_files = list(clip_dir.glob("*.mp4"))
310
+
311
+ # Sort by modification time (newest first)
312
+ mp4_files.sort(key=lambda f: f.stat().st_mtime, reverse=True)
313
+
314
+ videos = [f.name for f in mp4_files]
315
+
316
+ if mp4_files:
317
+ latest_video = mp4_files[0].name
318
+
319
+ results.append({
320
+ "sha1": sha1_dir.name,
321
+ "video_name": latest_video
322
+ })
323
+
324
+ return results