taotoolkit 1.0.0__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,626 @@
1
+ #!/usr/bin/env python
2
+ # -*- encoding: utf-8 -*-
3
+ """普通命令"""
4
+
5
+ import logging
6
+ import os
7
+ import sys
8
+ import subprocess
9
+ import argparse
10
+ import time
11
+ from typing import Optional
12
+ from datetime import datetime
13
+
14
+
15
+ from PIL import Image, ImageOps
16
+
17
+ from .tao_common import (
18
+ debugCtrl,
19
+ eExit,
20
+ logd,
21
+ loge,
22
+ logi,
23
+ oCmd,
24
+ rCmd,
25
+ sleep,
26
+ normalize_args,
27
+ register_command,
28
+ )
29
+
30
+
31
+ def drawFigureFromTxt(inFile, xlabel="x", ylabel="y"):
32
+ import matplotlib.pyplot as plt
33
+
34
+ x_data = []
35
+ y_data = []
36
+ with open(inFile, "r") as file:
37
+ for line in file:
38
+ if len(line.split(" ")) >= 6:
39
+ try:
40
+ x = float(line.split(" ")[2])
41
+ y = float(line.split(" ")[5])
42
+ x_data.append(x)
43
+ y_data.append(y)
44
+ except ValueError:
45
+ continue
46
+ plt.figure(figsize=(10, 6))
47
+ plt.plot(x_data, y_data, color="b", label="Data Curve", linewidth=0.3)
48
+ plt.title("Curve Plot of X vs Y", fontsize=14)
49
+ plt.xlabel(xlabel, fontsize=12)
50
+ plt.ylabel(ylabel, fontsize=12)
51
+ plt.grid(True, linestyle="-.", alpha=0.7, lw=0.35)
52
+ plt.legend()
53
+ plt.tight_layout()
54
+ plt.show()
55
+
56
+
57
+ def regTest():
58
+ txt = "ae.log"
59
+ drawFigureFromTxt(txt, "reg", "luma")
60
+
61
+
62
+ ######### logcat #########
63
+ # ── Emoji 兼容 ────────────────────────────────────────────
64
+ if sys.platform == "win32":
65
+ EMOJI = {
66
+ "ok": "[OK]",
67
+ "device": "[DEV]",
68
+ "save": "[LOG]",
69
+ "stop": "[STOP]",
70
+ "warn": "[WARN]",
71
+ }
72
+ else:
73
+ EMOJI = {
74
+ "ok": "✅",
75
+ "device": "📱",
76
+ "save": "💾",
77
+ "stop": "🛑",
78
+ "warn": "❌",
79
+ }
80
+
81
+
82
+ # ── Windows 控制台编码修复 ────────────────────────────────
83
+ def fix_windows_encoding():
84
+ """将 Windows 控制台切换为 UTF-8 输出"""
85
+ if sys.platform != "win32":
86
+ return
87
+ import ctypes
88
+
89
+ ctypes.windll.kernel32.SetConsoleOutputCP(65001)
90
+ ctypes.windll.kernel32.SetConsoleCP(65001)
91
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore
92
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace") # type: ignore
93
+
94
+
95
+ # ── 颜色支持检测 ──────────────────────────────────────────
96
+ def supports_ansi():
97
+ """判断当前终端是否支持 ANSI 转义色"""
98
+ if sys.platform == "win32":
99
+ import ctypes
100
+
101
+ kernel32 = ctypes.windll.kernel32
102
+ kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
103
+ return (
104
+ os.environ.get("TERM") != "" or "WT_SESSION" in os.environ or "ANSICON" in os.environ or sys.stdout.isatty()
105
+ )
106
+ return sys.stdout.isatty()
107
+
108
+
109
+ USE_COLOR = supports_ansi()
110
+
111
+
112
+ def highlight(line, keywords):
113
+ """关键词高亮,不支持颜色时原样返回"""
114
+ if not USE_COLOR or not keywords:
115
+ return line
116
+ for kw in keywords:
117
+ idx = line.lower().find(kw.lower())
118
+ if idx != -1:
119
+ line = line[:idx] + f"\033[1;31m{line[idx:idx + len(kw)]}\033[0m" + line[idx + len(kw) :]
120
+ return line
121
+
122
+
123
+ # ── 参数解析 ──────────────────────────────────────────────
124
+ def logcatParseArgs(argsList):
125
+ parser = argparse.ArgumentParser(prog="logcat", description="Android logcat 抓取工具")
126
+ parser.add_argument("-f", "--file", metavar="FILE", default=None, help="日志保存文件名(不指定则以时间戳自动命名)")
127
+ parser.add_argument(
128
+ "-k", "--keywords", metavar="KEYWORD", nargs="*", default=[], help="过滤关键词(空格分隔,不指定则输出全部)"
129
+ )
130
+ parser.add_argument("-d", "--device", metavar="SERIAL", default=None, help="指定设备序列号(多设备时使用)")
131
+ parser.add_argument("--no-file", action="store_true", help="只输出到终端,不保存文件")
132
+ return parser.parse_args(argsList)
133
+
134
+
135
+ # ── ADB 相关 ──────────────────────────────────────────────
136
+ def check_adb():
137
+ """检查 adb 是否可用,设备是否连接"""
138
+ try:
139
+ result = subprocess.run(["adb", "devices"], capture_output=True, text=True, timeout=5)
140
+ lines = [l.strip() for l in result.stdout.splitlines() if l.strip() and "List of devices" not in l]
141
+ if not lines:
142
+ print(f"{EMOJI['warn']} 没有检测到 Android 设备,请确认 USB 已连接且开启调试模式")
143
+ sys.exit(1)
144
+ print(f"{EMOJI['ok']} 检测到设备: {', '.join(lines)}")
145
+ except FileNotFoundError:
146
+ print(f"{EMOJI['warn']} 找不到 adb 命令,请确认 Android SDK 已安装并加入 PATH")
147
+ sys.exit(1)
148
+
149
+
150
+ def build_adb_cmd(device=None):
151
+ cmd = ["adb"]
152
+ if device:
153
+ cmd += ["-s", device]
154
+ cmd += ["logcat", "-v", "threadtime"]
155
+ return cmd
156
+
157
+
158
+ # ── 过滤 ──────────────────────────────────────────────────
159
+ def line_matches(line, keywords):
160
+ """检查一行是否包含任意关键词(不区分大小写)"""
161
+ if not keywords:
162
+ return True
163
+ low = line.lower()
164
+ return any(kw.lower() in low for kw in keywords)
165
+
166
+
167
+ # ── 主流程 ────────────────────────────────────────────────
168
+ def runLogcat(args):
169
+ check_adb()
170
+
171
+ # 确定日志文件名
172
+ log_path = None
173
+ if not args.no_file:
174
+ if args.file:
175
+ log_path = args.file
176
+ else:
177
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
178
+ log_path = f"logcat_{timestamp}.log"
179
+
180
+ keywords = args.keywords
181
+ cmd = build_adb_cmd(args.device)
182
+
183
+ logi(f"{EMOJI['device']} 开始抓取 logcat" f"{'(过滤: ' + ', '.join(keywords) + ')' if keywords else ''}")
184
+ if log_path:
185
+ logi(f"{EMOJI['save']} 日志保存至: {os.path.abspath(log_path)}")
186
+ logi("按 Ctrl+C 停止\n" + "-" * 60)
187
+
188
+ file_handle = open(log_path, "w", encoding="utf-8") if log_path else None
189
+ matched_count = 0
190
+ process = None
191
+
192
+ try:
193
+ process = subprocess.Popen(
194
+ cmd,
195
+ stdout=subprocess.PIPE,
196
+ stderr=subprocess.STDOUT,
197
+ encoding="utf-8",
198
+ errors="replace",
199
+ )
200
+
201
+ assert process.stdout is not None # 告诉类型检查器这里一定不为 None
202
+ for line in process.stdout:
203
+ line = line.rstrip("\n")
204
+
205
+ if file_handle:
206
+ file_handle.write(line + "\n")
207
+ file_handle.flush()
208
+
209
+ if not line_matches(line, keywords):
210
+ continue
211
+
212
+ matched_count += 1
213
+ print(highlight(line, keywords))
214
+
215
+ except KeyboardInterrupt:
216
+ print(f"\n{'-' * 60}")
217
+ logi(f"{EMOJI['stop']} 已停止,共捕获 {matched_count} 条日志")
218
+ if log_path:
219
+ logi(f"{EMOJI['save']} 日志已保存至: {os.path.abspath(log_path)}")
220
+ finally:
221
+ if file_handle:
222
+ file_handle.close()
223
+ if process:
224
+ try:
225
+ process.terminate()
226
+ except Exception:
227
+ pass
228
+
229
+
230
+ def CLI_logcat(args):
231
+ fix_windows_encoding()
232
+ argList = logcatParseArgs(args)
233
+ runLogcat(argList)
234
+
235
+
236
+ ######### logcat end #########
237
+
238
+
239
+ class Timer:
240
+ def __init__(self):
241
+ import tkinter as Tkinter
242
+
243
+ self.running = False
244
+ root = Tkinter.Tk()
245
+ root.title("timer")
246
+
247
+ # Fixing the window size.
248
+ root.minsize(width=250, height=70)
249
+ label = Tkinter.Label(root, text="Welcome!", fg="black", font="Verdana 30 bold")
250
+ label.pack()
251
+ f = Tkinter.Frame(root)
252
+ self.button1 = Tkinter.Button(f, text="Start", width=6, command=lambda: self.click(label))
253
+ self.reset = Tkinter.Button(f, text="Reset", width=6, state="disabled", command=lambda: self.tiemReset(label))
254
+ f.pack(anchor="center", pady=5)
255
+ self.button1.pack(side="left")
256
+ self.reset.pack(side="left")
257
+ root.mainloop()
258
+
259
+ def counter_label(self, label):
260
+ def gettime():
261
+ if self.running:
262
+ elap = time.time() - star # 获取时间差
263
+ minutes = int(elap / 60)
264
+ seconds = int(elap - minutes * 60.0)
265
+ hseconds = int((elap - minutes * 60.0 - seconds) * 1000)
266
+ string = "%02d:%02d:%03d" % (minutes, seconds, hseconds)
267
+ label["text"] = string
268
+ label.after(1, gettime) # 每隔1ms调用函数自身获取时间
269
+
270
+ star = time.time()
271
+ gettime()
272
+
273
+ def click(self, label):
274
+ if self.button1["text"] == "Start":
275
+ self.running = True
276
+ self.counter_label(label)
277
+ self.button1["text"] = "Stop"
278
+ self.reset["state"] = "disabled"
279
+ else:
280
+ self.button1["text"] = "Start"
281
+ self.reset["state"] = "active"
282
+ self.running = False
283
+
284
+ def tiemReset(self, label):
285
+ if self.running == False:
286
+ self.reset["state"] = "disabled"
287
+ label["text"] = "Welcome!"
288
+
289
+
290
+ def CLI_timer(args):
291
+ timer = Timer()
292
+
293
+
294
+ ROTATE_ANGLES = {
295
+ "90": Image.Transpose.ROTATE_90,
296
+ "180": Image.Transpose.ROTATE_180,
297
+ "270": Image.Transpose.ROTATE_270,
298
+ "mirror": Image.Transpose.FLIP_LEFT_RIGHT,
299
+ "flip": Image.Transpose.FLIP_TOP_BOTTOM,
300
+ }
301
+
302
+ # 有效角度列表,用于快速校验
303
+ VALID_ANGLES = frozenset(ROTATE_ANGLES.keys())
304
+
305
+
306
+ def rotateJpg(inFile: str, angle: str, outFile: Optional[str] = None, quality: int = 100) -> bool:
307
+ """
308
+ 旋转或翻转 JPEG 图片,保留 EXIF 信息
309
+
310
+ Args:
311
+ inFile: 输入图片路径
312
+ angle: 旋转角度,支持 "90", "180", "270", "mirror", "flip"
313
+ outFile: 输出路径,若为 None 则覆盖原文件
314
+ quality: 保存质量 (1-100),默认 100
315
+
316
+ Returns:
317
+ bool: 成功返回 True,失败返回 False
318
+
319
+ Raises:
320
+ ValueError: 角度参数无效时抛出
321
+ FileNotFoundError: 输入文件不存在时抛出
322
+ """
323
+ # 1. 参数校验
324
+ if not inFile:
325
+ raise ValueError("输入文件路径不能为空")
326
+
327
+ if angle not in VALID_ANGLES:
328
+ raise ValueError(f"angle error, support [90, 180, 270, flip, mirror], got: {angle}")
329
+
330
+ if not os.path.exists(inFile):
331
+ raise FileNotFoundError(f"文件不存在: {inFile}")
332
+
333
+ # 2. 设置日志级别(只执行一次)
334
+ logging.getLogger("PIL").setLevel(logging.ERROR)
335
+
336
+ # 3. 处理输出路径
337
+ if outFile is None:
338
+ outFile = inFile
339
+
340
+ try:
341
+ # 4. 打开图片并应用 EXIF 方向
342
+ image = Image.open(inFile)
343
+ if image is None:
344
+ raise ValueError(f"无法打开图片: {inFile}")
345
+
346
+ # 应用 EXIF 方向(自动处理图片旋转到正立)
347
+ image = ImageOps.exif_transpose(image)
348
+ if image is None:
349
+ raise ValueError(f"无法处理 EXIF 方向: {inFile}")
350
+
351
+ # 提取 EXIF 数据(用于保存时保留)
352
+ exif_data = _extract_exif(image)
353
+
354
+ # 5. 执行旋转/翻转
355
+ rotated = image.transpose(ROTATE_ANGLES[angle])
356
+ if rotated is None:
357
+ raise ValueError(f"旋转操作失败: {inFile}")
358
+
359
+ # 6. 保存图片
360
+ if is_jpeg(inFile):
361
+ _save_jpeg_with_exif(rotated, outFile, exif_data, quality)
362
+ else:
363
+ rotated.save(outFile)
364
+
365
+ logi(f"rotete {inFile} {angle} to {outFile}")
366
+
367
+ # 7. 清理资源
368
+ image.close()
369
+ rotated.close()
370
+
371
+ return True
372
+
373
+ except Exception as e:
374
+ loge(f"旋转图片失败 [{inFile}]: {e}")
375
+ return False
376
+
377
+
378
+ def _extract_exif(image: Image.Image) -> Optional[bytes]:
379
+ """
380
+ 从图片中提取 EXIF 数据
381
+
382
+ Args:
383
+ image: PIL Image 对象
384
+
385
+ Returns:
386
+ EXIF 字节数据,如果没有则返回 None
387
+ """
388
+ try:
389
+ if hasattr(image, "getexif"):
390
+ exif = image.getexif()
391
+ if exif:
392
+ return exif.tobytes()
393
+ elif hasattr(image, "info") and image.info:
394
+ exif = image.info.get("exif")
395
+ if isinstance(exif, bytes):
396
+ return exif
397
+ except Exception:
398
+ # 提取 EXIF 失败时静默返回 None
399
+ pass
400
+ return None
401
+
402
+
403
+ def _save_jpeg_with_exif(image: Image.Image, outFile: str, exif_data: Optional[bytes], quality: int) -> None:
404
+ """
405
+ 保存 JPEG 图片并保留 EXIF
406
+
407
+ Args:
408
+ image: PIL Image 对象
409
+ outFile: 输出路径
410
+ exif_data: EXIF 字节数据
411
+ quality: 保存质量
412
+ """
413
+ save_kwargs = {
414
+ "quality": quality,
415
+ "optimize": True, # 优化文件大小
416
+ "progressive": True, # 渐进式 JPEG,提升加载体验
417
+ }
418
+
419
+ # 优先使用 exif 参数
420
+ if exif_data:
421
+ save_kwargs["exif"] = exif_data
422
+ # 兼容旧版本 PIL,尝试从图片对象获取
423
+ elif hasattr(image, "getexif"):
424
+ try:
425
+ exif = image.getexif()
426
+ if exif:
427
+ save_kwargs["exif"] = exif.tobytes()
428
+ except Exception:
429
+ pass
430
+
431
+ image.save(outFile, **save_kwargs)
432
+
433
+
434
+ def is_jpeg(image_path: str) -> bool:
435
+ """检查文件是否为 JPEG 格式"""
436
+ try:
437
+ with Image.open(image_path) as img:
438
+ fmt = img.format
439
+ return fmt is not None and fmt.lower() in {"jpeg", "jpg"}
440
+ except Exception:
441
+ return False
442
+
443
+
444
+ def rotateJpgBatch(path, orientation, suffix):
445
+ for f in os.listdir(path):
446
+ if f.find(suffix) == -1:
447
+ if f.lower().endswith(".jpg") or f.lower().endswith(".jpeg"):
448
+ raw_name = os.path.join(path, f)
449
+ out_name = os.path.join(path, os.path.splitext(f)[0] + f"{suffix}.jpg")
450
+ rotateJpg(raw_name, orientation, out_name)
451
+
452
+
453
+ def CLI_rotateJpg(argsList):
454
+ parser = argparse.ArgumentParser(prog="rotateJpg", description="jpg旋转")
455
+ parser.add_argument(
456
+ "-p", "--path", type=str, required=True, default="", help="RAW文件路径或文件名,路径则处理所有raw/mipiraw文件"
457
+ )
458
+ parser.add_argument("-o", "--orientation", type=str, required=True, choices=["mirror", "flip", "90", "180", "270"])
459
+ parser.add_argument("-s", "--suffix", type=str, required=False, default="_rot", help="输出文件添加后缀")
460
+ args = parser.parse_args(argsList)
461
+
462
+ if os.path.isdir(args.path):
463
+ rotateJpgBatch(args.path, args.orientation, args.suffix)
464
+ else:
465
+ rotateJpg(args.path, args.orientation, os.path.splitext(args.path)[0] + f"{args.suffix}.jpg")
466
+
467
+
468
+ def CLI_suspend(argsList):
469
+ parser = argparse.ArgumentParser(prog="sus", description="suspend time")
470
+ parser.add_argument("-t", "--time", type=float, required=False, default=1, help="time 小时")
471
+ args = parser.parse_args(argsList)
472
+
473
+ sleep(args.time * 3600)
474
+ rCmd("echo AAbbcc123 |sudo -S systemctl suspend", showCmd=True, timeOut=30, shell=True, splitCmd=False)
475
+
476
+
477
+ def CLI_prop(args):
478
+ print("adb shell setprop persist.vendor.camera.enable.settingfile 1 \t\t打开tuning参数")
479
+ print(
480
+ "adb shell setprop persist.vendor.camera.enable.capturedump 1 \t\t打开dump VRF,文件dump在/data/vendor/camera/"
481
+ )
482
+ print("adb shell setprop persist.vendor.camera.force.nv12 1 \t\t\t预览强制出NV12")
483
+ print("adb shell setprop persist.vendor.camera.enable.streamdump 1 \t\t预览dump在/data/vendor/camera/")
484
+ print(
485
+ "adb shell setprop persist.vendor.camera.frameInfo.log.enable 1 \t\t关键字frameInfo frameId, 一帧的frameinfo分成2次"
486
+ )
487
+ print(
488
+ "adb shell setprop persist.vendor.camera.ispdata.dump 1 \t\t\t每帧tuning dump在/data/vendor/camera/tuning_debug"
489
+ )
490
+ print("adb shell setprop persist.vendor.camera.enable.OTP 1 \t\t打开OTP加载, 需kill cameraserver")
491
+ print("adb shell setprop persist.vendor.camera.sensor.regdump 1 \t\t打开dump sensor register")
492
+ print(
493
+ "adb shell setprop persist.vendor.camera.osd.dump 1 \t\tosd功能,打开dump isp info 到/data/vendor/camera/isp.txt"
494
+ )
495
+ print("adb shell setprop persist.vendor.camera.ispwritesensor.disable 1 \t\t关闭ISP写 sensor register")
496
+ print("adb shell setprop persist.vendor.camera.ultra.scene.mode ultra \t\t强制拍真50M")
497
+ print("adb shell setprop persist.vendor.camera.file2file.fps 15 \t\t修改filecamera帧率")
498
+
499
+
500
+ def CLI_eisCalc(args):
501
+ infoAll = [
502
+ {
503
+ "sensorName": "s5kjn1",
504
+ "m_nInputWidth": 4080,
505
+ "m_nBinningWidth": 2040,
506
+ "PixelPhysicalSize_um": 1.28,
507
+ "LensFocalLength_mm": 4.71,
508
+ "LineTime_ns": 4747,
509
+ },
510
+ {
511
+ "sensorName": "gc32e1",
512
+ "m_nInputWidth": 3264,
513
+ "m_nBinningWidth": 2172,
514
+ "PixelPhysicalSize_um": 1.4,
515
+ "LensFocalLength_mm": 2.9,
516
+ "LineTime_ns": 12372,
517
+ },
518
+ {
519
+ "sensorName": "s5k3l8",
520
+ "m_nInputWidth": 4208,
521
+ "m_nBinningWidth": 2104,
522
+ "PixelPhysicalSize_um": 1.12,
523
+ "LensFocalLength_mm": 3.45,
524
+ "LineTime_ns": 10256,
525
+ },
526
+ ]
527
+ for info in infoAll:
528
+ m_nFocalLength = info["LensFocalLength_mm"] / 1000
529
+ m_nSensorWidth = info["m_nInputWidth"] * info["PixelPhysicalSize_um"] / 1000000
530
+ m_nSensorHeight = info["m_nInputWidth"] * 9 / 16.0 * info["PixelPhysicalSize_um"] / 1000000
531
+ m_nReadoutTime = info["m_nBinningWidth"] * 9 / 16.0 * info["LineTime_ns"] / 1000000000
532
+ logi(f"{info['sensorName']} m_nFocalLength = {m_nFocalLength:.6f} m_nSensorWidth = {m_nSensorWidth:.6f} \
533
+ m_nSensorHeight = {m_nSensorHeight:.6f} m_nReadoutTime = {m_nReadoutTime:.6f}")
534
+
535
+
536
+ def ffmpepProc(args):
537
+ startTime_s = 1
538
+ totalTime_s = 9
539
+ infile1 = "eis_a55_0.mp4"
540
+ outfile = "eis_a55.mp4"
541
+ # 从第1秒开始,裁剪9秒
542
+ cmd = f"ffmpeg -ss 00:00:{startTime_s} -t 00:00:{totalTime_s} -i {infile1} -c {outfile}"
543
+
544
+ # 上下拼接,同帧率,同分辨率
545
+ cmd = r'ffmpeg -i eis_a5d23.mp4 -i eis_a55.mp4 -filter_complex "[0:v][1:v]vstack=inputs=2" output.mp4'
546
+
547
+ # 左右拼接,同帧率,同分辨率
548
+ cmd = r'ffmpeg -i a5d23_1.mp4 -i a55.mp4 -filter_complex "[0:v][1:v]hstack=inputs=2" -c:v libx264 -crf 18 -preset slow output.mp4'
549
+
550
+ # 裁掉视频最上面的40行像素
551
+ cmd = r'ffmpeg -i a5d23.mp4 -vf "crop=in_w:in_h-40:0:40" -c:v libx264 -crf 18 -preset slow -c:a copy a5d23_1.mp4'
552
+
553
+ # 在左上角和左中下添加文字水印,cmd中使用去掉\
554
+ cmd = r"ffmpeg -i output.mp4 -vf \"drawtext=text='A5D23':fontcolor=red:fontsize=96:x=10:y=10, drawtext=text='A55':fontcolor=red:fontsize=96:x=10:y=h/2+10\" -c:a copy out.mp4"
555
+ # cmd = r"ffmpeg -i output.mp4 -vf "drawtext=text='ASR8662(A5D23)':fontcolor=red:fontsize=96:x=10:y=10, drawtext=text='9863A(ZTE A55)':fontcolor=red:fontsize=96:x=10:y=h/2+10" -c:a copy out.mp4"
556
+
557
+ # 视频解帧
558
+ cmd = r"ffmpeg -i IMG_1406.mov -r 30 -f image2 image-%3d.bmp"
559
+
560
+
561
+ def CLI_videoCut(args=None):
562
+ scene = 1
563
+ v1 = f"smart9_scene{scene}"
564
+ v2 = f"lark_scene{scene}"
565
+ for i in range(5):
566
+ cmd = f'ffmpeg -i {v1}_{i}.mp4 -i {v2}_{i}.mp4 -filter_complex "[0:v][1:v]hstack=inputs=2" -c:v libx264 -crf 18 -preset slow scene{scene}_{i}.mp4'
567
+ oCmd(cmd, showCmd=True, logLevel="info")
568
+
569
+
570
+ def CLI_video2jpg(argsList):
571
+ parser = argparse.ArgumentParser(prog="video2jpg", description="convert video to jpg")
572
+ parser.add_argument("-p", "--path", type=str, required=True, default="", help="视频文件")
573
+ parser.add_argument("-f", "--fps", type=str, required=False, default="30", help="视频帧率")
574
+ parser.add_argument("-o", "--out", type=str, required=False, default="v2j", help="解出jpg文件夹")
575
+ args = parser.parse_args(argsList)
576
+
577
+ os.makedirs(os.path.join(os.path.curdir, args.out), exist_ok=True)
578
+ cmd = f"ffmpeg -i {args.path} -r {args.fps} -f image2 {args.out}/image-%3d.bmp"
579
+ logi(f"video2jpg cmd: {cmd}")
580
+ oCmd(cmd, showCmd=True, logLevel="info")
581
+
582
+
583
+ _COMMAND_REGISTRY = {}
584
+
585
+
586
+ def printHelp():
587
+ print("[tool]")
588
+ print("timer :start a GUI timer")
589
+ print("rotjpg -h :rotate jpg")
590
+ print("sus -h :suspend system")
591
+ print("prop :show property commands")
592
+ print("eis :calculate eis info")
593
+ print("vc :cut video")
594
+ print("v2j -h :convert video to jpg")
595
+ print("logcat -h :抓取andriod日志")
596
+
597
+
598
+ CLI_timer = register_command("timer", registry=_COMMAND_REGISTRY)(CLI_timer)
599
+ CLI_rotateJpg = register_command("rotjpg", registry=_COMMAND_REGISTRY)(CLI_rotateJpg)
600
+ CLI_suspend = register_command("sus", registry=_COMMAND_REGISTRY)(CLI_suspend)
601
+ CLI_prop = register_command("prop", registry=_COMMAND_REGISTRY)(CLI_prop)
602
+ CLI_eisCalc = register_command("eis", registry=_COMMAND_REGISTRY)(CLI_eisCalc)
603
+ CLI_videoCut = register_command("vc", registry=_COMMAND_REGISTRY)(CLI_videoCut)
604
+ CLI_video2jpg = register_command("v2j", registry=_COMMAND_REGISTRY)(CLI_video2jpg)
605
+ CLI_logcat = register_command("logcat", registry=_COMMAND_REGISTRY)(CLI_logcat)
606
+
607
+
608
+ def cmdToolMain(args):
609
+ debugCtrl(1) if "debug" in args else debugCtrl(0)
610
+ args.remove("debug") if "debug" in args else args
611
+ args = normalize_args(args)
612
+ if len(args) < 1 or args[0] in {"help", "-h", "--help"}:
613
+ printHelp()
614
+ return
615
+ cmd = args[0]
616
+ handler = _COMMAND_REGISTRY.get(cmd)
617
+ if handler is None:
618
+ loge(f"unsupport command {cmd}")
619
+ return
620
+ handler(args[1:])
621
+
622
+
623
+ if __name__ == "__main__":
624
+ debugCtrl(1) if "debug" in sys.argv else debugCtrl(0)
625
+ sys.argv.remove("debug") if "debug" in sys.argv else sys.argv
626
+ cmdToolMain(sys.argv)