pykitool 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,697 @@
1
+ import fnmatch
2
+ import hashlib
3
+ import os
4
+ import platform
5
+ import re
6
+ import shutil
7
+ import subprocess
8
+ import tempfile
9
+ import time
10
+ import zipfile
11
+ from pathlib import Path
12
+ from typing import Any, BinaryIO, Dict, List, Optional, Tuple, Union
13
+
14
+ from loguru import logger
15
+
16
+ # ================================ 路径处理 ================================
17
+
18
+
19
+ # 统一转换路径为 POSIX 格式
20
+ def ap(path: Optional[str]) -> str:
21
+ if path:
22
+ if path.startswith("http://") or path.startswith("https://"):
23
+ return path
24
+ else:
25
+ return Path(path).as_posix()
26
+ return ""
27
+
28
+
29
+ # 获取文件名(完整)
30
+ def fullname(path: str) -> str:
31
+ return os.path.basename(path)
32
+
33
+
34
+ # 获取文件名称
35
+ def filename(path: str) -> str:
36
+ _, filename, _ = name_and_format(path)
37
+ return filename
38
+
39
+
40
+ # 获取文件后缀
41
+ def fileformat(path: str) -> str:
42
+ _, _, fileformat = name_and_format(path)
43
+ return fileformat
44
+
45
+
46
+ # 获取文件名和后缀(返回完整文件名、文件名、扩展名)
47
+ def name_and_format(path: str) -> Tuple[str, str, str]:
48
+ path = ap(path)
49
+ fullname = os.path.basename(path)
50
+ filename, fileformat = os.path.splitext(fullname)
51
+ return fullname, filename, fileformat
52
+
53
+
54
+ # ================================ 文件读写 ================================
55
+
56
+
57
+ # 读取文件内容(支持单个路径或多个路径)
58
+ def read(paths: Union[str, bytes, List[str], Tuple[str, ...]]) -> Optional[str]:
59
+ if isinstance(paths, (str, bytes)):
60
+ paths = [paths]
61
+ elif not isinstance(paths, (list, tuple)):
62
+ return None
63
+
64
+ content_list: List[str] = []
65
+ for path in paths:
66
+ try:
67
+ with open(path, "r", encoding="utf-8") as file:
68
+ content = file.read()
69
+ content_list.append(content)
70
+ logger.trace(f"Successfully read file: {path}")
71
+ except FileNotFoundError:
72
+ logger.error(f"File not found: {path}")
73
+ except PermissionError:
74
+ logger.error(f"Permission denied: {path}")
75
+ except IOError as e:
76
+ logger.error(f"IO error reading file {path}: {str(e)}")
77
+ except Exception as e:
78
+ logger.error(f"Error reading file {path}: {str(e)}")
79
+
80
+ if content_list:
81
+ return "\n\n".join(content_list)
82
+ return None
83
+
84
+
85
+ # 写入文件内容
86
+ def write(path: Union[str, Path], data: str) -> Optional[str]:
87
+ try:
88
+ path_str = str(path)
89
+ with open(path_str, "w", encoding="utf-8") as file:
90
+ file.write(data)
91
+ return path_str
92
+ except FileNotFoundError:
93
+ logger.error(f"File not found: {path}")
94
+ except PermissionError:
95
+ logger.error(f"Permission denied: {path}")
96
+ except IOError as e:
97
+ logger.error(f"IO error writing to file {path}: {str(e)}")
98
+ except Exception as e:
99
+ logger.error(f"Error writing to file {path}: {str(e)}")
100
+ return None
101
+
102
+
103
+ # ================================ 文件操作 ================================
104
+
105
+
106
+ # 拷贝文件到目标位置
107
+ def cp(source_file: Union[str, Path], target: Union[str, Path]) -> Optional[str]:
108
+ source_file = str(source_file)
109
+ target = str(target)
110
+ file_name = os.path.basename(source_file)
111
+
112
+ if os.path.isdir(target):
113
+ target_file = os.path.join(target, file_name)
114
+ else:
115
+ target_file = target
116
+
117
+ target_dir = os.path.dirname(target_file)
118
+
119
+ try:
120
+ mk_folder(target_dir)
121
+ shutil.copy2(source_file, target_file)
122
+ logger.debug(f"File {source_file} copied to: {target_file}")
123
+ return target_file
124
+ except FileNotFoundError:
125
+ logger.error(f"Source file not found: {source_file}")
126
+ except PermissionError:
127
+ logger.error(f"Permission denied copying to: {target_file}")
128
+ except shutil.SameFileError:
129
+ logger.error(f"Source and target are the same file: {source_file}")
130
+ except OSError as e:
131
+ logger.error(f"OS error copying file: {str(e)}")
132
+ except Exception as e:
133
+ logger.error(f"Error copying file: {str(e)}")
134
+ return None
135
+
136
+
137
+ # 移动文件到目标位置
138
+ def mv(source_file: Union[str, Path], target: Union[str, Path]) -> Optional[str]:
139
+ source_file = str(source_file)
140
+ target = str(target)
141
+ file_name = os.path.basename(source_file)
142
+
143
+ if os.path.isdir(target):
144
+ target_file = os.path.join(target, file_name)
145
+ else:
146
+ target_file = target
147
+
148
+ target_dir = os.path.dirname(target_file)
149
+
150
+ try:
151
+ mk_folder(target_dir)
152
+ shutil.move(source_file, target_file)
153
+ logger.debug(f"File {source_file} moved to: {target_file}")
154
+ return target_file
155
+ except FileNotFoundError:
156
+ logger.error(f"Source file not found: {source_file}")
157
+ except PermissionError:
158
+ logger.error(f"Permission denied moving to: {target_file}")
159
+ except shutil.SameFileError:
160
+ logger.error(f"Source and target are the same file: {source_file}")
161
+ except OSError as e:
162
+ logger.error(f"OS error moving file: {str(e)}")
163
+ except Exception as e:
164
+ logger.error(f"Error moving file: {str(e)}")
165
+ return None
166
+
167
+
168
+ # 拷贝文件夹到目标位置
169
+ def cp_folder(source_folder: Union[str, Path], target_folder: Union[str, Path]) -> None:
170
+ try:
171
+ shutil.copytree(str(source_folder), str(target_folder), dirs_exist_ok=True)
172
+ logger.debug(f"Folder {source_folder} copied to: {target_folder}")
173
+ except shutil.Error as e:
174
+ logger.error(f"Error copying folder: {str(e)}")
175
+ raise
176
+
177
+
178
+ # 新建文件夹(可选清空已有内容)
179
+ def mk_folder(path: Union[str, Path], is_clean: bool = False, exist_ok: bool = True) -> str:
180
+ output_path = Path(path)
181
+ if is_clean:
182
+ rm_folder(str(path))
183
+ output_path.mkdir(parents=True, exist_ok=exist_ok)
184
+ return str(output_path)
185
+
186
+
187
+ # 安全删除文件
188
+ def rm(path: Optional[str]) -> None:
189
+ try:
190
+ if not path:
191
+ logger.error("Invalid path: None or empty string")
192
+ return
193
+
194
+ # 防止删除系统根目录
195
+ unsafe_paths = ["/", "C:\\", "C:/", os.path.expanduser("~")]
196
+ if path in unsafe_paths:
197
+ logger.error("Unsafe delete attempt blocked: {}", path)
198
+ return
199
+
200
+ if os.path.exists(path):
201
+ os.remove(path)
202
+ logger.trace("Successfully deleted file: {}", path)
203
+ else:
204
+ logger.warning("File does not exist: {}", path)
205
+ except PermissionError:
206
+ logger.error(f"Permission denied deleting file: {path}")
207
+ except OSError as e:
208
+ logger.error(f"OS error deleting file {path}: {str(e)}")
209
+ except Exception as e:
210
+ logger.error(f"Error deleting file {path}: {str(e)}")
211
+
212
+
213
+ # 安全删除文件夹
214
+ def rm_folder(path: Optional[str]) -> None:
215
+ folder_path: Optional[str] = None
216
+ try:
217
+ if not path:
218
+ logger.error("Invalid path: None or empty string")
219
+ return
220
+
221
+ path = os.path.abspath(path)
222
+
223
+ if os.path.isfile(path):
224
+ folder_path = os.path.dirname(path)
225
+ elif os.path.isdir(path):
226
+ folder_path = path
227
+ else:
228
+ logger.warning("Path does not exist: {}", path)
229
+ return
230
+
231
+ # 防止删除系统根目录
232
+ unsafe_paths = ["/", "C:\\", "C:/", os.path.expanduser("~")]
233
+ if folder_path in unsafe_paths:
234
+ logger.error("Unsafe delete attempt blocked: {}", folder_path)
235
+ return
236
+
237
+ if os.path.exists(folder_path):
238
+ shutil.rmtree(folder_path)
239
+ logger.debug("Successfully deleted folder: {}", folder_path)
240
+ else:
241
+ logger.warning("Folder does not exist: {}", folder_path)
242
+ except PermissionError:
243
+ logger.error(f"Permission denied deleting folder: {folder_path}")
244
+ except OSError as e:
245
+ logger.error(f"OS error deleting folder {folder_path}: {str(e)}")
246
+ except Exception as e:
247
+ logger.error(f"Error deleting folder {folder_path}: {str(e)}")
248
+
249
+
250
+ # 覆盖文件(可选备份原文件)
251
+ def overwrite(source: Union[str, Path], target: Union[str, Path], backup: bool = True) -> None:
252
+ source = str(source)
253
+ target = str(target)
254
+ try:
255
+ if not os.path.exists(source):
256
+ raise FileNotFoundError(f"Source file does not exist: {source}")
257
+ if backup and os.path.exists(target):
258
+ name_part, ext = os.path.splitext(target)
259
+ timestamp = time.strftime("%Y%m%d%H%M%S")
260
+ shutil.move(target, f"{name_part}_{timestamp}{ext}")
261
+ shutil.move(source, target)
262
+ logger.debug(f"File overwritten: {target}")
263
+ except FileNotFoundError as e:
264
+ raise FileNotFoundError(f"Overwrite failed: {str(e)}") from e
265
+ except PermissionError as e:
266
+ raise PermissionError(f"Permission denied: {str(e)}") from e
267
+ except Exception as e:
268
+ raise RuntimeError(f"Failed to overwrite {target} with {source}: {str(e)}") from e
269
+
270
+
271
+ # ================================ 文件信息 ================================
272
+
273
+
274
+ # 计算文件 MD5 值(优化块大小为 64KB 提升大文件性能)
275
+ def md5(file_obj: BinaryIO) -> str:
276
+ md5_hash = hashlib.md5()
277
+ # 使用 64KB 块大小,比 4KB 更适合大文件
278
+ for chunk in iter(lambda: file_obj.read(65536), b""):
279
+ md5_hash.update(chunk)
280
+ return md5_hash.hexdigest()
281
+
282
+
283
+ # 根据文件路径计算 MD5 值
284
+ def md5_file(path: Union[str, Path]) -> str:
285
+ with open(str(path), "rb") as f:
286
+ return md5(f)
287
+
288
+
289
+ # 获取文件大小(单位:MB)
290
+ def get_file_size_MB(file_path: Union[str, Path]) -> float:
291
+ return os.path.getsize(str(file_path)) / (1024 * 1024)
292
+
293
+
294
+ # 验证路径是否有效且存在
295
+ def is_valid_path(p: Any) -> bool:
296
+ return isinstance(p, (str, Path)) and str(p).strip() != "" and Path(p).exists()
297
+
298
+
299
+ # 验证文件夹是否包含有效图片
300
+ def is_valid_image_folder(path: Union[str, Path], extensions: Tuple[str, ...] = (".png", ".jpg", ".jpeg")) -> bool:
301
+ p = Path(path)
302
+ if not p.exists() or not p.is_dir():
303
+ return False
304
+ images = list(p.glob("*"))
305
+ return any(img.suffix.lower() in extensions for img in images)
306
+
307
+
308
+ # 清理文件名中的非法字符
309
+ def clean_filename(text: Optional[str]) -> str:
310
+ if not text:
311
+ return "untitled"
312
+ text = re.sub(r'[\\/:*?"<>|]', "_", text.strip())
313
+ text = text.strip(".")
314
+ if not text:
315
+ return "untitled"
316
+ return text
317
+
318
+
319
+ # ================================ 符号链接 ================================
320
+
321
+
322
+ # 创建符号链接
323
+ def symlink(src: Union[str, Path], dest: Union[str, Path]) -> None:
324
+ src = os.path.abspath(str(src))
325
+ dest = os.path.abspath(str(dest))
326
+ system = platform.system()
327
+
328
+ try:
329
+ if os.path.islink(dest):
330
+ current_target = os.readlink(dest)
331
+ if os.path.abspath(current_target) != src:
332
+ os.unlink(dest)
333
+ _create_symlink(src, dest, system)
334
+ logger.info(f"[Symlink overwritten] {dest} -> {src}")
335
+ else:
336
+ logger.info(f"[Symlink ok] {dest} -> {src}")
337
+ else:
338
+ if os.path.exists(dest):
339
+ if os.path.isdir(dest):
340
+ shutil.rmtree(dest)
341
+ else:
342
+ rm(dest)
343
+ _create_symlink(src, dest, system)
344
+ logger.info(f"[Symlink created] {dest} -> {src}")
345
+ except OSError as e:
346
+ logger.error(f"OS error creating symlink: {str(e)}")
347
+ except Exception as e:
348
+ logger.error(f"Error creating symlink: {str(e)}")
349
+
350
+
351
+ # 创建符号链接辅助函数(根据操作系统选择方式)
352
+ def _create_symlink(src: str, dest: str, system: str) -> None:
353
+ if system != "Windows":
354
+ os.symlink(src, dest)
355
+ else:
356
+ subprocess.check_call(["cmd", "/c", "mklink", "/J", dest, src], shell=True)
357
+
358
+
359
+ # ================================ 目录遍历 ================================
360
+
361
+
362
+ # 获取指定文件夹下所有文件(返回字典 {文件名: 路径})
363
+ def sub_files(path: Union[str, Path]) -> Dict[str, str]:
364
+ result: Dict[str, str] = {}
365
+ path = str(path)
366
+ try:
367
+ for entry in os.listdir(path):
368
+ full_path = os.path.join(path, entry)
369
+ if os.path.isfile(full_path):
370
+ key = os.path.splitext(entry)[0]
371
+ result[key] = full_path
372
+ except OSError as e:
373
+ logger.error(f"Error listing files in {path}: {str(e)}")
374
+ return result
375
+
376
+
377
+ # 获取指定文件夹下子文件夹(可排除指定模式)
378
+ def sub_folders(path: Union[str, Path], exclude: str = "") -> List[str]:
379
+ patterns = [p.strip() for p in exclude.replace(";", ",").split(",") if p.strip()]
380
+ path = str(path)
381
+ try:
382
+ if not os.path.isdir(path):
383
+ return []
384
+ all_dirs = [name for name in os.listdir(path) if os.path.isdir(os.path.join(path, name))]
385
+ filtered_dirs = [name for name in all_dirs if not any(fnmatch.fnmatch(name, pattern) for pattern in patterns)]
386
+ return filtered_dirs
387
+ except OSError as e:
388
+ logger.error(f"Error listing folders in {path}: {str(e)}")
389
+ return []
390
+
391
+
392
+ # 获取目录中指定索引前缀的文件
393
+ def directory_idx(path: Union[str, Path], idx: int = 0) -> Tuple[Optional[str], Optional[str]]:
394
+ path = str(path)
395
+ try:
396
+ for filename in os.listdir(path):
397
+ if filename.lower().startswith(f"{idx:02d}_"):
398
+ file_path = ap(os.path.join(path, filename))
399
+ if os.path.isfile(file_path):
400
+ return file_path, os.path.splitext(filename)[0]
401
+ except OSError as e:
402
+ logger.error(f"Error accessing directory {path}: {str(e)}")
403
+ return None, None
404
+
405
+
406
+ # 从基础目录提取相对路径
407
+ def relative_path(path: Union[str, Path], base_dir: str = "webapp") -> Path:
408
+ path = Path(path)
409
+ parts = list(path.parts)
410
+ try:
411
+ base_index = parts.index(base_dir)
412
+ relative_parts = parts[base_index + 1 :]
413
+ return Path(*relative_parts) if relative_parts else Path(".")
414
+ except ValueError:
415
+ return path
416
+
417
+
418
+ # ================================ 临时文件 ================================
419
+
420
+
421
+ # 获取或创建临时目录
422
+ def tempdir(root_dir: str = "tools") -> str:
423
+ temp_dir = tempfile.gettempdir()
424
+ temp_dir_root = os.path.join(temp_dir, root_dir.lower())
425
+ mk_folder(temp_dir_root)
426
+ return temp_dir_root
427
+
428
+
429
+ # 获取唯一临时文件夹路径
430
+ def temp_folder() -> str:
431
+ return ap(os.path.join(tempdir(), next(tempfile._get_candidate_names())))
432
+
433
+
434
+ # 获取临时视频文件路径
435
+ def temp_video_mp4(prefix: str = "", suffix: str = "", ext: str = "mp4") -> str:
436
+ prefix = prefix or next(tempfile._get_candidate_names())
437
+ suffix_str = f"_{suffix}" if suffix else ""
438
+ return ap(os.path.join(tempdir(), f"{prefix}{suffix_str}.{ext}"))
439
+
440
+
441
+ # 获取临时音频文件路径
442
+ def temp_audio_wav(prefix: str = "", suffix: str = "", ext: str = "wav") -> str:
443
+ prefix = prefix or next(tempfile._get_candidate_names())
444
+ suffix_str = f"_{suffix}" if suffix else ""
445
+ return ap(os.path.join(tempdir(), f"{prefix}{suffix_str}.{ext}"))
446
+
447
+
448
+ # 创建临时处理目录并拷贝源文件
449
+ def temp_process_path(path: str, rebuild: bool = False, output: Optional[str] = None) -> Tuple[str, str]:
450
+ file_name = filename(path)
451
+ if rebuild and output:
452
+ rm_folder(output)
453
+ if output:
454
+ mk_folder(output)
455
+ temp_file_path = ap(os.path.join(output, os.path.basename(path)))
456
+ cp(path, temp_file_path)
457
+ logger.info("{} -> save as: {}", path, output)
458
+ return file_name, temp_file_path
459
+ return file_name, path
460
+
461
+
462
+ # 拷贝文件到系统临时目录
463
+ def move_to_temp(path: Union[str, Path]) -> str:
464
+ path = str(path)
465
+ filename = os.path.basename(path)
466
+ temp_dir = tempfile.gettempdir()
467
+ temp_path = os.path.join(temp_dir, filename)
468
+ shutil.copy(path, temp_path)
469
+ return temp_path
470
+
471
+
472
+ # ================================ 压缩解压 ================================
473
+
474
+
475
+ # 压缩文件/文件夹到 zip 归档
476
+ def zip_files(paths: List[Union[str, Path]], output: Union[str, Path]) -> None:
477
+ output = str(output)
478
+ try:
479
+ with zipfile.ZipFile(output, "w", zipfile.ZIP_DEFLATED) as zipf:
480
+ for path in paths:
481
+ path = str(path)
482
+ if os.path.isfile(path):
483
+ zipf.write(path, arcname=os.path.basename(path))
484
+ elif os.path.isdir(path):
485
+ for root, _, files in os.walk(path):
486
+ for file in files:
487
+ full_path = os.path.join(root, file)
488
+ rel_path = os.path.relpath(full_path, start=os.path.dirname(path))
489
+ zipf.write(full_path, arcname=rel_path)
490
+ logger.debug(f"Files compressed to: {output}")
491
+ except zipfile.BadZipFile as e:
492
+ logger.error(f"Bad zip file error: {str(e)}")
493
+ raise
494
+ except OSError as e:
495
+ logger.error(f"OS error creating zip: {str(e)}")
496
+ raise
497
+
498
+
499
+ # 解压 zip 归档到目录
500
+ def unzip(path: Union[str, Path], extract_dir: Union[str, Path], password: Optional[str] = None) -> None:
501
+ path = str(path)
502
+ extract_dir = str(extract_dir)
503
+
504
+ if not zipfile.is_zipfile(path):
505
+ raise ValueError(f"Invalid zip file: {path}")
506
+
507
+ mk_folder(extract_dir)
508
+ try:
509
+ with zipfile.ZipFile(path, "r") as zip_ref:
510
+ if password:
511
+ zip_ref.setpassword(password.encode("utf-8"))
512
+ zip_ref.extractall(extract_dir)
513
+ logger.debug(f"Files extracted to: {extract_dir}")
514
+ except zipfile.BadZipFile as e:
515
+ logger.error(f"Bad zip file: {str(e)}")
516
+ raise
517
+ except RuntimeError as e:
518
+ logger.error(f"Runtime error extracting zip (wrong password?): {str(e)}")
519
+ raise
520
+
521
+
522
+ # ================================ 调用示例 ================================
523
+
524
+
525
+ if __name__ == "__main__":
526
+ # 测试路径(请根据实际情况修改)
527
+ test_file = "D:/Projects/test/file.txt"
528
+ test_folder = "D:/Projects/test"
529
+
530
+ # ==================== 路径处理示例 ====================
531
+
532
+ # 路径转换为 POSIX 格式
533
+ # result = ap("D:\\Projects\\test\\file.txt")
534
+ # logger.info("POSIX path: {}", result)
535
+
536
+ # 获取文件名(不含后缀)
537
+ # result = filename("D:/Projects/test/video.mp4")
538
+ # logger.info("File name: {}", result)
539
+
540
+ # 获取完整文件名(含后缀)
541
+ # result = fullname("D:/Projects/test/video.mp4")
542
+ # logger.info("Full name: {}", result)
543
+
544
+ # 获取文件名和后缀
545
+ # full, filename, ext = name_and_suffix("D:/Projects/test/video.mp4")
546
+ # logger.info("Name and suffix: {} | {} | {}", full, filename, ext)
547
+
548
+ # ==================== 文件读写示例 ====================
549
+
550
+ # 读取文件内容
551
+ # content = read(test_file)
552
+ # logger.info("File content: {}", content)
553
+
554
+ # 写入文件内容
555
+ # result = write("D:/Projects/test/output.txt", "Hello World")
556
+ # logger.info("Write result: {}", result)
557
+
558
+ # ==================== 文件操作示例 ====================
559
+
560
+ # 拷贝文件
561
+ # result = cp("D:/Projects/test/source.txt", "D:/Projects/test/backup/")
562
+ # logger.info("Copy result: {}", result)
563
+
564
+ # 移动文件
565
+ # result = mv("D:/Projects/test/source.txt", "D:/Projects/test/archive/")
566
+ # logger.info("Move result: {}", result)
567
+
568
+ # 拷贝文件夹
569
+ # cp_folder("D:/Projects/test/src", "D:/Projects/test/backup")
570
+ # logger.info("Folder copied")
571
+
572
+ # 新建文件夹
573
+ # result = mk_folder("D:/Projects/test/new_folder", is_clean=True)
574
+ # logger.info("Folder created: {}", result)
575
+
576
+ # 删除文件
577
+ # rm("D:/Projects/test/temp.txt")
578
+ # logger.info("File deleted")
579
+
580
+ # 删除文件夹
581
+ # rm_folder("D:/Projects/test/temp_folder")
582
+ # logger.info("Folder deleted")
583
+
584
+ # 覆盖文件
585
+ # overwrite("D:/Projects/test/new.txt", "D:/Projects/test/old.txt", backup=True)
586
+ # logger.info("File overwritten")
587
+
588
+ # ==================== 文件信息示例 ====================
589
+
590
+ # 计算文件 MD5 值
591
+ # result = md5_file(test_file)
592
+ # logger.info("MD5: {}", result)
593
+
594
+ # 获取文件大小(MB)
595
+ # result = get_file_size_MB(test_file)
596
+ # logger.info("File size: {} MB", result)
597
+
598
+ # 验证路径是否有效
599
+ # result = is_valid_path(test_folder)
600
+ # logger.info("Is valid path: {}", result)
601
+
602
+ # 验证图片文件夹是否有效
603
+ # result = is_valid_image_folder("D:/Projects/images")
604
+ # logger.info("Is valid image folder: {}", result)
605
+
606
+ # 清理非法文件名字符
607
+ # result = clean_filename("test:file*name?.txt")
608
+ # logger.info("Cleaned filename: {}", result)
609
+
610
+ # ==================== 符号链接示例 ====================
611
+
612
+ # 创建符号链接
613
+ # symlink("D:/Projects/source", "D:/Projects/link")
614
+ # logger.info("Symlink created")
615
+
616
+ # ==================== 目录遍历示例 ====================
617
+
618
+ # 获取指定文件夹下所有文件
619
+ # result = sub_files(test_folder)
620
+ # logger.info("Sub files: {}", result)
621
+
622
+ # 获取指定文件夹下子文件夹
623
+ # result = sub_folders(test_folder, exclude="__pycache__,*.egg-info")
624
+ # logger.info("Sub folders: {}", result)
625
+
626
+ # 获取目录中指定索引前缀的文件
627
+ # file_path, file_name = directory_idx("D:/Projects/test/tts", 0)
628
+ # logger.info("Directory idx: {} | {}", file_path, file_name)
629
+
630
+ # 获取相对路径
631
+ # result = relative_path("D:/Projects/webapp/static/js/app.js", "webapp")
632
+ # logger.info("Relative path: {}", result)
633
+
634
+ # ==================== 临时文件示例 ====================
635
+
636
+ # 获取临时目录
637
+ # result = tempdir()
638
+ # logger.info("Temp directory: {}", result)
639
+
640
+ # 获取临时文件夹路径
641
+ # result = temp_folder()
642
+ # logger.info("Temp folder: {}", result)
643
+
644
+ # 获取临时视频文件路径
645
+ # result = temp_video_mp4(prefix="test", suffix="clip")
646
+ # logger.info("Temp video path: {}", result)
647
+
648
+ # 获取临时音频文件路径
649
+ # result = temp_audio_wav(prefix="test", suffix="segment")
650
+ # logger.info("Temp audio path: {}", result)
651
+
652
+ # 创建临时处理目录
653
+ # file_name, temp_path = temp_process_path("D:/video.mp4", output="D:/temp/process")
654
+ # logger.info("Temp process: {} | {}", file_name, temp_path)
655
+
656
+ # 移动文件到临时目录
657
+ # result = move_to_temp(test_file)
658
+ # logger.info("Moved to temp: {}", result)
659
+
660
+ # ==================== 压缩解压示例 ====================
661
+
662
+ # 压缩文件
663
+ # zip_files(["D:/Projects/test/file1.txt", "D:/Projects/test/folder"], "D:/output.zip")
664
+ # logger.info("Files zipped")
665
+
666
+ # 解压文件
667
+ # unzip("D:/output.zip", "D:/extracted")
668
+ # logger.info("Files unzipped")
669
+
670
+ # ==================== 下载示例 ====================
671
+
672
+ # 使用 yt-dlp 下载视频
673
+ # result = ytdlp_download(url="https://www.youtube.com/watch?v=xxxxx")
674
+ # logger.info("Downloaded video: {}", result)
675
+
676
+ # HTTP 下载文件
677
+ # result = http_download("https://example.com/file.zip", save_folder="D:/downloads")
678
+ # logger.info("Downloaded file: {}", result)
679
+
680
+ # ==================== SRT 字幕示例 ====================
681
+
682
+ # 加载 SRT 字幕文件
683
+ # segments = load_srt("D:/Projects/test/subtitle.srt")
684
+ # logger.info("Loaded segments: {}", len(segments))
685
+
686
+ # 写入 SRT 字幕文件
687
+ # data = [{"start": 0.0, "end": 2.5, "text": "Hello"}, {"start": 2.5, "end": 5.0, "text": "World"}]
688
+ # result = write_srt(data, "D:/Projects/test/output.srt")
689
+ # logger.info("SRT written: {}", result)
690
+
691
+ # 移除空白和重复字幕
692
+ # result = remove_blank_and_duplicate_srt("D:/Projects/test/subtitle.srt")
693
+ # logger.info("SRT cleaned: {}", result)
694
+
695
+ # 从 JSON 生成 SRT 字幕
696
+ # result = generate_srt("D:/Projects/test/data.json", "text", "D:/Projects/test/output.srt")
697
+ # logger.info("SRT generated: {}", result)