22from os import PathLike
33from pathlib import Path
44from re import compile as re_compile
5+ import traceback
56
67from aiofile import async_open
78
@@ -94,6 +95,10 @@ async def run(self) -> None:
9495 """
9596 处理主体
9697 """
98+
99+ # BDMV 处理相关变量初始化
100+ self .bdmv_collections : dict [str , list [tuple [AlistPath , int ]]] = {} # BDMV目录 -> [(文件路径, 文件大小)]
101+ self .bdmv_largest_files : dict [str , AlistPath ] = {} # BDMV目录 -> 最大文件路径
97102
98103 def filter (path : AlistPath ) -> bool :
99104 """
@@ -107,10 +112,25 @@ def filter(path: AlistPath) -> bool:
107112 if path .is_dir :
108113 return False
109114
115+ # 跳过系统文件夹和不需要的文件
116+ if any (folder in path .full_path for folder in ["@eaDir" , "Thumbs.db" , ".DS_Store" ]):
117+ return False
118+
119+ # 完全跳过 BDMV 文件夹内的所有文件(除了我们特殊处理的 .m2ts 文件)
120+ if "/BDMV/" in path .full_path and not self ._is_bdmv_file (path ):
121+ logger .debug (f"跳过 BDMV 文件夹内的文件: { path .name } " )
122+ return False
123+
110124 if path .suffix .lower () not in self .process_file_exts :
111125 logger .debug (f"文件 { path .name } 不在处理列表中" )
112126 return False
113127
128+ # 检查是否为 BDMV 文件
129+ if self ._is_bdmv_file (path ):
130+ self ._collect_bdmv_file (path )
131+ # 暂时不处理,等收集完所有文件后再决定
132+ return False
133+
114134 try :
115135 local_path = self .__get_local_path (path )
116136 except OSError as e : # 可能是文件名过长
@@ -152,15 +172,52 @@ def filter(path: AlistPath) -> bool:
152172
153173 self .processed_local_paths = set () # 云盘文件对应的本地文件路径
154174
175+ # 第一阶段:收集所有文件信息并直接处理普通文件
155176 async with self .__max_workers , TaskGroup () as tg :
156177 async for path in self .client .iter_path (
157178 dir_path = self .source_dir ,
158179 wait_time = self .wait_time ,
159180 is_detail = is_detail ,
160181 filter = filter ,
161182 ):
183+ # 直接处理普通文件,不需要额外的 list
162184 tg .create_task (self .__file_processer (path ))
163185
186+ # 完成 BDMV 文件收集,确定最大文件
187+ self ._finalize_bdmv_collections ()
188+
189+ # 第二阶段:处理 BDMV 最大文件
190+ logger .info (f"开始处理 { len (self .bdmv_largest_files )} 个 BDMV 目录" )
191+ for bdmv_root , largest_file in self .bdmv_largest_files .items ():
192+ try :
193+ logger .info (f"处理 BDMV 目录: { bdmv_root } " )
194+ logger .info (f"最大文件: { largest_file .full_path } " )
195+
196+ # 重新获取详细信息以确保有 raw_url
197+ if self .mode == "RawURL" and not largest_file .raw_url :
198+ logger .debug (f"重新获取 BDMV 文件详细信息: { largest_file .full_path } " )
199+ try :
200+ updated_path = await self .client .async_api_fs_get (largest_file .full_path )
201+ # 保持原有的 full_path,只更新其他属性
202+ original_full_path = largest_file .full_path
203+ largest_file = updated_path
204+ largest_file .full_path = original_full_path
205+ except Exception as e :
206+ logger .warning (f"重新获取 BDMV 文件详细信息失败: { e } " )
207+
208+ # 处理文件
209+ await self .__file_processer (largest_file )
210+
211+ # 添加到已处理路径列表
212+ local_path = self .__get_local_path (largest_file )
213+ self .processed_local_paths .add (local_path )
214+
215+ logger .info (f"BDMV 文件处理完成: { largest_file .name } " )
216+ except Exception as e :
217+ logger .error (f"处理 BDMV 文件 { largest_file .full_path } 时出错:{ e } " )
218+ logger .error (f"详细错误信息: { traceback .format_exc ()} " )
219+ continue
220+
164221 if self .sync_server :
165222 await self .__cleanup_local_files ()
166223 logger .info ("清理过期的 .strm 文件完成" )
@@ -173,7 +230,9 @@ async def __file_processer(self, path: AlistPath) -> None:
173230 :param path: AlistPath 对象
174231 """
175232 local_path = self .__get_local_path (path )
233+ logger .debug (f"__file_processer: 处理文件 { path .full_path } -> 本地路径 { local_path } | 模式 { self .mode } " )
176234
235+ # 统一的 URL 生成逻辑,BDMV 文件与普通文件使用相同的逻辑
177236 if self .mode == "AlistURL" :
178237 content = path .download_url
179238 elif self .mode == "RawURL" :
@@ -183,9 +242,21 @@ async def __file_processer(self, path: AlistPath) -> None:
183242 else :
184243 raise ValueError (f"AlistStrm 未知的模式 { self .mode } " )
185244
245+ logger .debug (f"__file_processer: 初始 content = { content } " )
246+
247+ # 如果 URL 为空,提供 fallback(适用于所有文件类型)
248+ if not content :
249+ if self .mode == "AlistURL" :
250+ content = f"{ self .client .url } /d{ path .full_path } "
251+ elif self .mode == "RawURL" :
252+ content = path .download_url or f"{ self .client .url } /d{ path .full_path } "
253+ elif self .mode == "AlistPath" :
254+ content = path .full_path
255+ logger .debug (f"__file_processer: fallback content = { content } " )
256+
186257 await to_thread (local_path .parent .mkdir , parents = True , exist_ok = True )
187258
188- logger .debug (f"开始处理 { local_path } " )
259+ logger .debug (f"开始处理 { local_path } | 内容: { content } " )
189260 if local_path .suffix == ".strm" :
190261 async with async_open (local_path , mode = "w" , encoding = "utf-8" ) as file :
191262 await file .write (content )
@@ -202,6 +273,27 @@ def __get_local_path(self, path: AlistPath) -> Path:
202273 :param path: AlistPath 对象
203274 :return: 本地文件路径
204275 """
276+ # 检查是否为 BDMV 文件
277+ if self ._is_bdmv_file (path ):
278+ bdmv_root = self ._get_bdmv_root_dir (path )
279+ if bdmv_root and self ._should_process_bdmv_file (path ):
280+ # 为 BDMV 文件生成特殊路径
281+ movie_title = self ._get_movie_title_from_bdmv_path (bdmv_root )
282+
283+ if self .flatten_mode :
284+ local_path = self .target_dir / f"{ movie_title } .strm"
285+ else :
286+ # 计算相对于 source_dir 的路径
287+ relative_path = bdmv_root .replace (self .source_dir , "" , 1 )
288+ if relative_path .startswith ("/" ):
289+ relative_path = relative_path [1 :]
290+
291+ # 将 .strm 文件放在电影根目录下,使用电影标题命名
292+ local_path = self .target_dir / relative_path / f"{ movie_title } .strm"
293+
294+ return local_path
295+
296+ # 原有逻辑保持不变
205297 if self .flatten_mode :
206298 local_path = self .target_dir / path .name
207299 else :
@@ -253,3 +345,92 @@ async def __cleanup_local_files(self) -> None:
253345 parent_dir = parent_dir .parent
254346 except Exception as e :
255347 logger .error (f"删除文件 { file_path } 失败:{ e } " )
348+
349+ def _is_bdmv_file (self , path : AlistPath ) -> bool :
350+ """
351+ 检查文件是否为 BDMV 结构中的 .m2ts 文件
352+
353+ :param path: AlistPath 对象
354+ :return: 是否为 BDMV 文件
355+ """
356+ return "/BDMV/STREAM/" in path .full_path and path .suffix .lower () == ".m2ts"
357+
358+ def _get_bdmv_root_dir (self , path : AlistPath ) -> str :
359+ """
360+ 获取 BDMV 文件的根目录路径
361+
362+ :param path: BDMV 中的文件路径
363+ :return: BDMV 根目录路径
364+ """
365+ full_path = path .full_path
366+ bdmv_index = full_path .find ("/BDMV/" )
367+ if bdmv_index != - 1 :
368+ return full_path [:bdmv_index ]
369+ return ""
370+
371+ def _get_movie_title_from_bdmv_path (self , bdmv_root : str ) -> str :
372+ """
373+ 从 BDMV 根目录路径提取电影标题
374+
375+ :param bdmv_root: BDMV 根目录路径
376+ :return: 电影标题
377+ """
378+ # 获取最后一个目录名作为电影标题
379+ return Path (bdmv_root ).name
380+
381+ def _collect_bdmv_file (self , path : AlistPath ) -> None :
382+ """
383+ 收集 BDMV 文件信息
384+
385+ :param path: BDMV 中的 .m2ts 文件路径
386+ """
387+ bdmv_root = self ._get_bdmv_root_dir (path )
388+ if not bdmv_root :
389+ return
390+
391+ if bdmv_root not in self .bdmv_collections :
392+ self .bdmv_collections [bdmv_root ] = []
393+
394+ # 添加文件信息到集合中
395+ self .bdmv_collections [bdmv_root ].append ((path , path .size ))
396+ logger .debug (f"收集 BDMV 文件: { path .full_path } , 大小: { path .size } " )
397+
398+ def _finalize_bdmv_collections (self ) -> None :
399+ """
400+ 完成 BDMV 文件收集,确定每个 BDMV 目录中的最大文件
401+ """
402+ for bdmv_root , files in self .bdmv_collections .items ():
403+ if not files :
404+ continue
405+
406+ movie_title = self ._get_movie_title_from_bdmv_path (bdmv_root )
407+ logger .info (f"BDMV 目录 '{ movie_title } ' 中发现 { len (files )} 个 .m2ts 文件:" )
408+
409+ # 按大小排序并显示所有文件
410+ sorted_files = sorted (files , key = lambda x : x [1 ], reverse = True )
411+ for i , (file_path , file_size ) in enumerate (sorted_files ):
412+ size_mb = file_size / (1024 * 1024 )
413+ status = "✓ 选中" if i == 0 else " 跳过"
414+ logger .info (f" { status } { file_path .name } : { size_mb :.1f} MB ({ file_size } 字节)" )
415+
416+ # 找出最大的文件
417+ largest_file = max (files , key = lambda x : x [1 ])
418+ self .bdmv_largest_files [bdmv_root ] = largest_file [0 ]
419+
420+ largest_size_mb = largest_file [1 ] / (1024 * 1024 )
421+ logger .info (f"BDMV 目录 '{ movie_title } ' 最终选择: { largest_file [0 ].name } ({ largest_size_mb :.1f} MB)" )
422+
423+ def _should_process_bdmv_file (self , path : AlistPath ) -> bool :
424+ """
425+ 检查 BDMV 文件是否应该被处理(即是否为最大文件)
426+
427+ :param path: BDMV 中的 .m2ts 文件路径
428+ :return: 是否应该处理
429+ """
430+ bdmv_root = self ._get_bdmv_root_dir (path )
431+ if not bdmv_root :
432+ return False
433+
434+ largest_file = self .bdmv_largest_files .get (bdmv_root )
435+ return largest_file is not None and largest_file .full_path == path .full_path
436+
0 commit comments