@@ -40,16 +40,28 @@ def _detect_media_type(filename: str) -> MediaType:
4040
4141
4242def _file_to_media (path : Path , root : Path ) -> MediaFile :
43- """Convert a filesystem path to a MediaFile model."""
43+ """Convert a filesystem path to a MediaFile model.
44+
45+ Files inside a subdirectory use the subdirectory name as the mission_id.
46+ Root-level files whose stem matches a subdirectory (e.g.
47+ ``recorder_20260411.mcap`` alongside ``recorder_20260411/``) are also
48+ associated with that mission.
49+ """
4450 stat = path .stat ()
4551 rel = path .relative_to (root )
52+ if len (rel .parts ) > 1 :
53+ mission_id = rel .parts [0 ]
54+ elif (root / path .stem ).is_dir ():
55+ mission_id = path .stem
56+ else :
57+ mission_id = None
4658 return MediaFile (
4759 id = str (rel ),
4860 filename = path .name ,
4961 media_type = _detect_media_type (path .name ),
5062 size_bytes = stat .st_size ,
5163 created_at = datetime .fromtimestamp (stat .st_mtime , tz = timezone .utc ),
52- mission_id = rel . parts [ 0 ] if len ( rel . parts ) > 1 else None ,
64+ mission_id = mission_id ,
5365 download_url = f"/api/v1/media/download/{ rel } " ,
5466 )
5567
@@ -75,10 +87,13 @@ async def get_media_files(
7587 self ,
7688 mission_id : str | None = None ,
7789 media_type : MediaType | None = None ,
78- limit : int = 50 ,
90+ limit : int = 0 ,
7991 offset : int = 0 ,
8092 ) -> list [MediaFile ]:
81- """List media files from the recorder directory."""
93+ """List media files from the recorder directory.
94+
95+ A *limit* of ``0`` (the default) returns all files.
96+ """
8297 try :
8398 search_root = self .media_root / mission_id if mission_id else self .media_root
8499 if not search_root .exists ():
@@ -101,14 +116,21 @@ async def get_media_files(
101116
102117 files .sort (key = lambda f : f .created_at , reverse = True )
103118
104- return files [offset : offset + limit ]
119+ if limit > 0 :
120+ return files [offset : offset + limit ]
121+ return files [offset :] if offset else files
105122
106123 except Exception as e :
107124 logger .warning (f"Failed to scan media files: { e } " )
108125 raise
109126
110127 async def get_missions_with_media (self ) -> list [MediaMission ]:
111- """Discover missions by scanning top-level subdirectories of the recorder."""
128+ """Discover missions by scanning top-level subdirectories of the recorder.
129+
130+ Root-level files whose stem matches a subdirectory (e.g.
131+ ``recorder_20260411.mcap`` next to ``recorder_20260411/``) are
132+ counted as part of that mission.
133+ """
112134 try :
113135 if not self .media_root .exists ():
114136 return []
@@ -142,6 +164,27 @@ async def get_missions_with_media(self) -> list[MediaMission]:
142164 except (FileNotFoundError , PermissionError ):
143165 continue
144166
167+ # Include root-level files whose stem matches this directory
168+ for sibling in self .media_root .iterdir ():
169+ try :
170+ if not sibling .is_file () or sibling .stem != entry .name :
171+ continue
172+ ext = sibling .suffix .lstrip ("." ).lower ()
173+ if ext not in ALL_EXTENSIONS :
174+ continue
175+ stat = sibling .stat ()
176+ total_size += stat .st_size
177+ latest_mtime = max (latest_mtime , stat .st_mtime )
178+ mt = _detect_media_type (sibling .name )
179+ if mt == MediaType .IMAGE :
180+ images += 1
181+ elif mt == MediaType .VIDEO :
182+ videos += 1
183+ else :
184+ data_files += 1
185+ except (FileNotFoundError , PermissionError ):
186+ continue
187+
145188 if images + videos + data_files == 0 :
146189 continue
147190
0 commit comments