修改日志结构
This commit is contained in:
122
app/logging_setup.py
Normal file
122
app/logging_setup.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
# stdlib 级别 → PQLogGUI 标签
|
||||||
|
_LEVEL_TO_GUI = {
|
||||||
|
logging.DEBUG: "debug",
|
||||||
|
logging.INFO: "info",
|
||||||
|
logging.WARNING: "warning",
|
||||||
|
logging.ERROR: "error",
|
||||||
|
logging.CRITICAL: "error",
|
||||||
|
}
|
||||||
|
|
||||||
|
# PQLogGUI 标签 → stdlib 级别
|
||||||
|
GUI_LEVEL_TO_LOG = {
|
||||||
|
"debug": logging.DEBUG,
|
||||||
|
"info": logging.INFO,
|
||||||
|
"success": logging.INFO,
|
||||||
|
"warning": logging.WARNING,
|
||||||
|
"error": logging.ERROR,
|
||||||
|
"separator": logging.INFO,
|
||||||
|
"blank": logging.DEBUG,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 用于在 LogRecord 上做 GUI 来源标记,避免回环
|
||||||
|
_FROM_GUI_FLAG = "_from_gui"
|
||||||
|
|
||||||
|
# 文件日志:头一行元信息,第二行正文,记录之间空行隔开,方便阅读
|
||||||
|
_FILE_LOG_FORMAT = "%(asctime)s [%(levelname)s] %(name)s\n%(message)s\n"
|
||||||
|
_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||||
|
|
||||||
|
_initialized = False
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_log_dir(log_dir: Optional[str]) -> str:
|
||||||
|
if log_dir:
|
||||||
|
return log_dir
|
||||||
|
# 默认放在工作目录下的 log/,方便用户直接打开查看
|
||||||
|
return os.path.join(os.getcwd(), "log")
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging(
|
||||||
|
level: int = logging.INFO,
|
||||||
|
log_dir: Optional[str] = None,
|
||||||
|
) -> str:
|
||||||
|
"""配置全局 logging,返回日志目录。可重复调用,第二次起为空操作。
|
||||||
|
|
||||||
|
- 不再向终端输出,避免污染控制台。
|
||||||
|
- 日志文件按日期命名:``log/YYYY-MM-DD.log``。
|
||||||
|
"""
|
||||||
|
global _initialized
|
||||||
|
root = logging.getLogger()
|
||||||
|
if _initialized:
|
||||||
|
return _resolve_log_dir(log_dir)
|
||||||
|
|
||||||
|
resolved = _resolve_log_dir(log_dir)
|
||||||
|
os.makedirs(resolved, exist_ok=True)
|
||||||
|
|
||||||
|
file_formatter = logging.Formatter(_FILE_LOG_FORMAT, datefmt=_DATE_FORMAT)
|
||||||
|
|
||||||
|
root.setLevel(level)
|
||||||
|
|
||||||
|
# 移除既有 handler,避免重复 / basicConfig 默认控制台输出
|
||||||
|
for handler in list(root.handlers):
|
||||||
|
root.removeHandler(handler)
|
||||||
|
|
||||||
|
today = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
log_file = os.path.join(resolved, f"{today}.log")
|
||||||
|
file_handler = logging.FileHandler(log_file, encoding="utf-8", delay=True)
|
||||||
|
file_handler.setLevel(logging.DEBUG)
|
||||||
|
file_handler.setFormatter(file_formatter)
|
||||||
|
root.addHandler(file_handler)
|
||||||
|
|
||||||
|
_initialized = True
|
||||||
|
logging.getLogger(__name__).info("日志系统初始化完成 dir=%s", resolved)
|
||||||
|
return resolved
|
||||||
|
|
||||||
|
|
||||||
|
class TkLogHandler(logging.Handler):
|
||||||
|
"""把 stdlib logging 的记录转发到 ``PQLogGUI`` 面板。
|
||||||
|
|
||||||
|
带 ``_from_gui`` 标记的记录会被忽略,避免 GUI → file → GUI 回环。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, log_gui):
|
||||||
|
super().__init__()
|
||||||
|
self._log_gui = log_gui
|
||||||
|
|
||||||
|
def emit(self, record: logging.LogRecord) -> None: # noqa: D401
|
||||||
|
if getattr(record, _FROM_GUI_FLAG, False):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
message = self.format(record)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
message = record.getMessage()
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
gui_level = _LEVEL_TO_GUI.get(record.levelno, "info")
|
||||||
|
try:
|
||||||
|
# PQLogGUI.log 内部已处理跨线程
|
||||||
|
self._log_gui.log(message, level=gui_level, _from_logging=True)
|
||||||
|
except Exception:
|
||||||
|
self.handleError(record)
|
||||||
|
|
||||||
|
|
||||||
|
def attach_gui_handler(log_gui) -> TkLogHandler:
|
||||||
|
"""把 ``PQLogGUI`` 注册为 root logger 的 handler。已存在则替换。"""
|
||||||
|
root = logging.getLogger()
|
||||||
|
# 移除旧的 TkLogHandler,保证只挂一个
|
||||||
|
for h in list(root.handlers):
|
||||||
|
if isinstance(h, TkLogHandler):
|
||||||
|
root.removeHandler(h)
|
||||||
|
handler = TkLogHandler(log_gui)
|
||||||
|
handler.setLevel(logging.INFO)
|
||||||
|
handler.setFormatter(logging.Formatter("%(name)s: %(message)s"))
|
||||||
|
root.addHandler(handler)
|
||||||
|
return handler
|
||||||
@@ -23,6 +23,7 @@ import uuid
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from dataclasses import dataclass, asdict
|
from dataclasses import dataclass, asdict
|
||||||
from typing import Callable, List, Optional
|
from typing import Callable, List, Optional
|
||||||
|
from urllib.error import HTTPError
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from urllib.request import Request, urlopen
|
from urllib.request import Request, urlopen
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ _SUPPORTED_IMG_EXT = (".png", ".jpg", ".jpeg", ".bmp", ".webp")
|
|||||||
# 测试环境后端
|
# 测试环境后端
|
||||||
API_BASE_URL = "http://10.201.44.70:9018/ai-agent/"
|
API_BASE_URL = "http://10.201.44.70:9018/ai-agent/"
|
||||||
API_PATH = "api/v1/pqtest/generate"
|
API_PATH = "api/v1/pqtest/generate"
|
||||||
API_TIMEOUT = 90.0 # 后端最长 60s,留余量
|
API_TIMEOUT = 300.0 # 后端最长 60s,留余量
|
||||||
|
|
||||||
# 进程级会话 id(多轮对话需保持一致),可通过 ``reset_session`` 重置
|
# 进程级会话 id(多轮对话需保持一致),可通过 ``reset_session`` 重置
|
||||||
_session_id: str = str(uuid.uuid4())
|
_session_id: str = str(uuid.uuid4())
|
||||||
@@ -62,7 +63,6 @@ def set_session_id(session_id: str) -> str:
|
|||||||
with _session_lock:
|
with _session_lock:
|
||||||
old = _session_id
|
old = _session_id
|
||||||
_session_id = sid
|
_session_id = sid
|
||||||
logger.info("[AIImage] 会话切换 %s -> %s", _mask_sid(old), _mask_sid(_session_id))
|
|
||||||
return _session_id
|
return _session_id
|
||||||
|
|
||||||
|
|
||||||
@@ -72,7 +72,6 @@ def reset_session() -> str:
|
|||||||
with _session_lock:
|
with _session_lock:
|
||||||
old = _session_id
|
old = _session_id
|
||||||
_session_id = str(uuid.uuid4())
|
_session_id = str(uuid.uuid4())
|
||||||
logger.info("[AIImage] 会话切换 %s -> %s", _mask_sid(old), _mask_sid(_session_id))
|
|
||||||
return _session_id
|
return _session_id
|
||||||
|
|
||||||
|
|
||||||
@@ -144,27 +143,66 @@ def _call_pqtest_generate(user_message: str, session_id: str, timeout: float = A
|
|||||||
"session_id": session_id},
|
"session_id": session_id},
|
||||||
ensure_ascii=False,
|
ensure_ascii=False,
|
||||||
).encode("utf-8")
|
).encode("utf-8")
|
||||||
|
request_headers = {
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"User-Agent": "pqAutomationApp/1.0",
|
||||||
|
}
|
||||||
endpoint = _api_endpoint()
|
endpoint = _api_endpoint()
|
||||||
logger.info(
|
logger.info(
|
||||||
"[AIImage] 请求生成 sid=%s prompt_len=%d prompt=%r",
|
"[AIImage] 请求生成 sid=%s prompt_len=%d prompt=%r",
|
||||||
_mask_sid(session_id), len(user_message or ""), _truncate(user_message),
|
_mask_sid(session_id), len(user_message or ""), _truncate(user_message),
|
||||||
)
|
)
|
||||||
logger.debug("[AIImage] POST %s timeout=%.1fs", endpoint, timeout)
|
logger.info(
|
||||||
|
"[AIImage][REQUEST]\\nendpoint=%s\\nmethod=POST\\ntimeout=%.1fs\\nheaders=%s\\nbody=%s",
|
||||||
|
endpoint,
|
||||||
|
timeout,
|
||||||
|
json.dumps(request_headers, ensure_ascii=False),
|
||||||
|
payload.decode("utf-8", errors="replace"),
|
||||||
|
)
|
||||||
request = Request(
|
request = Request(
|
||||||
endpoint,
|
endpoint,
|
||||||
data=payload,
|
data=payload,
|
||||||
method="POST",
|
method="POST",
|
||||||
headers={
|
headers=request_headers,
|
||||||
"Content-Type": "application/json; charset=utf-8",
|
|
||||||
"Accept": "application/json",
|
|
||||||
"User-Agent": "pqAutomationApp/1.0",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
t0 = time.monotonic()
|
t0 = time.monotonic()
|
||||||
try:
|
try:
|
||||||
with urlopen(request, timeout=timeout) as response:
|
with urlopen(request, timeout=timeout) as response:
|
||||||
raw = response.read()
|
raw = response.read()
|
||||||
http_status = response.status
|
http_status = response.status
|
||||||
|
response_headers = dict(response.headers.items())
|
||||||
|
raw_text = raw.decode("utf-8", errors="replace")
|
||||||
|
logger.info(
|
||||||
|
"[AIImage][RESPONSE]\\nstatus=%s\\nheaders=%s\\nbody=%s",
|
||||||
|
http_status,
|
||||||
|
json.dumps(response_headers, ensure_ascii=False),
|
||||||
|
raw_text,
|
||||||
|
)
|
||||||
|
except HTTPError as exc:
|
||||||
|
elapsed = time.monotonic() - t0
|
||||||
|
err_raw = b""
|
||||||
|
try:
|
||||||
|
err_raw = exc.read() or b""
|
||||||
|
except Exception:
|
||||||
|
err_raw = b""
|
||||||
|
err_text = err_raw.decode("utf-8", errors="replace") if err_raw else ""
|
||||||
|
err_headers = {}
|
||||||
|
try:
|
||||||
|
if exc.headers is not None:
|
||||||
|
err_headers = dict(exc.headers.items())
|
||||||
|
except Exception:
|
||||||
|
err_headers = {}
|
||||||
|
logger.error(
|
||||||
|
"[AIImage][RESPONSE_ERROR] sid=%s elapsed=%.2fs status=%s reason=%s\\nheaders=%s\\nbody=%s",
|
||||||
|
_mask_sid(session_id),
|
||||||
|
elapsed,
|
||||||
|
getattr(exc, "code", "?"),
|
||||||
|
str(exc),
|
||||||
|
json.dumps(err_headers, ensure_ascii=False),
|
||||||
|
err_text,
|
||||||
|
)
|
||||||
|
raise
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
elapsed = time.monotonic() - t0
|
elapsed = time.monotonic() - t0
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -173,15 +211,13 @@ def _call_pqtest_generate(user_message: str, session_id: str, timeout: float = A
|
|||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
elapsed = time.monotonic() - t0
|
elapsed = time.monotonic() - t0
|
||||||
logger.debug(
|
logger.info("[AIImage] HTTP %s 收到 %d bytes elapsed=%.2fs", http_status, len(raw), elapsed)
|
||||||
"[AIImage] HTTP %s 收到 %d bytes elapsed=%.2fs",
|
|
||||||
http_status, len(raw), elapsed,
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
result = json.loads(raw.decode("utf-8"))
|
result = json.loads(raw.decode("utf-8"))
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error("[AIImage] 响应解析失败 sid=%s raw=%r", _mask_sid(session_id), raw[:200])
|
raw_text = raw.decode("utf-8", errors="replace")
|
||||||
raise RuntimeError(f"AI 接口返回非 JSON:{raw[:200]!r}") from exc
|
logger.error("[AIImage] 响应解析失败 sid=%s raw=%s", _mask_sid(session_id), raw_text)
|
||||||
|
raise RuntimeError(f"AI 接口返回非 JSON:{raw_text}") from exc
|
||||||
|
|
||||||
code = result.get("code")
|
code = result.get("code")
|
||||||
message = result.get("message") or ""
|
message = result.get("message") or ""
|
||||||
|
|||||||
@@ -187,11 +187,12 @@ def toggle_ai_image_panel(self):
|
|||||||
# ---------------- 列表 / 选中 ----------------
|
# ---------------- 列表 / 选中 ----------------
|
||||||
|
|
||||||
|
|
||||||
def reload_ai_image_list(self):
|
def reload_ai_image_list(self, auto_select_first=True):
|
||||||
"""重新扫描缓存并刷新列表。
|
"""重新扫描缓存并刷新列表。
|
||||||
|
|
||||||
按 ``session_id`` 分组:每个会话用一行不可选的分隔头(``── 会话 #N · 时间 ──``),
|
按 ``session_id`` 分组:每个会话用一行不可选的分隔头(``── 会话 #N · 时间 ──``),
|
||||||
其下列出该轮生成的所有图片。会话按"最近使用"倒序,组内按时间倒序。
|
其下列出该轮生成的所有图片。会话按"最近使用"倒序,组内按时间倒序。
|
||||||
|
auto_select_first: 是否自动选中第一张图片(默认 True)。
|
||||||
"""
|
"""
|
||||||
self.ai_image_records = _svc.list_records()
|
self.ai_image_records = _svc.list_records()
|
||||||
self.ai_image_listbox.delete(0, tk.END)
|
self.ai_image_listbox.delete(0, tk.END)
|
||||||
@@ -222,7 +223,7 @@ def reload_ai_image_list(self):
|
|||||||
flat.append(rec)
|
flat.append(rec)
|
||||||
# 替换为按显示顺序展平后的列表,便于其它逻辑(前一张/后一张等)
|
# 替换为按显示顺序展平后的列表,便于其它逻辑(前一张/后一张等)
|
||||||
self.ai_image_records = flat
|
self.ai_image_records = flat
|
||||||
if self.ai_image_records:
|
if self.ai_image_records and auto_select_first:
|
||||||
# 选中第一张实际记录
|
# 选中第一张实际记录
|
||||||
for row, ridx in enumerate(self._ai_image_row_map):
|
for row, ridx in enumerate(self._ai_image_row_map):
|
||||||
if ridx is not None:
|
if ridx is not None:
|
||||||
@@ -333,7 +334,7 @@ def _start_new_session(self):
|
|||||||
return
|
return
|
||||||
_svc.reset_session()
|
_svc.reset_session()
|
||||||
self.ai_image_status_var.set("已开启新对话")
|
self.ai_image_status_var.set("已开启新对话")
|
||||||
reload_ai_image_list(self)
|
reload_ai_image_list(self, auto_select_first=False)
|
||||||
|
|
||||||
|
|
||||||
def _session_id_for_row(self, row: int) -> str:
|
def _session_id_for_row(self, row: int) -> str:
|
||||||
@@ -372,8 +373,7 @@ def _update_request_progress(self):
|
|||||||
if not getattr(self, "_ai_image_requesting", False):
|
if not getattr(self, "_ai_image_requesting", False):
|
||||||
self._ai_image_progress_job = None
|
self._ai_image_progress_job = None
|
||||||
return
|
return
|
||||||
phases = ["后端处理中…", "正在生成图片…", "正在下载结果…", "即将完成…"]
|
self.ai_image_status_var.set("正在生成图片…")
|
||||||
self.ai_image_status_var.set(phases[self._ai_image_progress_phase % len(phases)])
|
|
||||||
self._ai_image_progress_phase += 1
|
self._ai_image_progress_phase += 1
|
||||||
self._ai_image_progress_job = self.root.after(900, lambda: _update_request_progress(self))
|
self._ai_image_progress_job = self.root.after(900, lambda: _update_request_progress(self))
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,22 @@
|
|||||||
|
import logging
|
||||||
import threading
|
import threading
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
import ttkbootstrap as ttk
|
import ttkbootstrap as ttk
|
||||||
|
|
||||||
|
|
||||||
|
# 与 app.logging_setup 共享的映射;放在这里避免循环 import
|
||||||
|
_GUI_LEVEL_TO_LOG = {
|
||||||
|
"debug": logging.DEBUG,
|
||||||
|
"info": logging.INFO,
|
||||||
|
"success": logging.INFO,
|
||||||
|
"warning": logging.WARNING,
|
||||||
|
"error": logging.ERROR,
|
||||||
|
"separator": logging.INFO,
|
||||||
|
"blank": logging.DEBUG,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class PQLogGUI(ttk.Frame):
|
class PQLogGUI(ttk.Frame):
|
||||||
VALID_LEVELS = {"info", "success", "warning", "error", "debug", "separator", "blank"}
|
VALID_LEVELS = {"info", "success", "warning", "error", "debug", "separator", "blank"}
|
||||||
|
|
||||||
@@ -10,6 +24,8 @@ class PQLogGUI(ttk.Frame):
|
|||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._line_count = 0
|
self._line_count = 0
|
||||||
self._max_lines = 1500
|
self._max_lines = 1500
|
||||||
|
# 与 stdlib logging 联通的 logger,由 logging_setup 配置 file handler
|
||||||
|
self._logger = logging.getLogger("pq.gui")
|
||||||
self.create_widgets()
|
self.create_widgets()
|
||||||
|
|
||||||
def create_widgets(self):
|
def create_widgets(self):
|
||||||
@@ -62,13 +78,25 @@ class PQLogGUI(ttk.Frame):
|
|||||||
self._configure_tags()
|
self._configure_tags()
|
||||||
self.log_text.config(state=tk.DISABLED)
|
self.log_text.config(state=tk.DISABLED)
|
||||||
|
|
||||||
def log(self, message, level="info"):
|
def log(self, message, level="info", _from_logging=False):
|
||||||
if threading.current_thread() is not threading.main_thread():
|
if threading.current_thread() is not threading.main_thread():
|
||||||
self.after(0, self.log, message, level)
|
self.after(0, self.log, message, level, _from_logging)
|
||||||
return
|
return
|
||||||
|
|
||||||
text = "" if message is None else str(message)
|
text = "" if message is None else str(message)
|
||||||
normalized_level = self._normalize_level(level, text)
|
normalized_level = self._normalize_level(level, text)
|
||||||
|
|
||||||
|
# 转发到 stdlib logging,落到文件(_from_logging=True 表示来源已是
|
||||||
|
# logging,不再回写避免重复)。带 _from_gui 标记,TkLogHandler 会跳过。
|
||||||
|
if not _from_logging and normalized_level != "blank":
|
||||||
|
log_level = _GUI_LEVEL_TO_LOG.get(normalized_level, logging.INFO)
|
||||||
|
try:
|
||||||
|
self._logger.log(
|
||||||
|
log_level, text, extra={"_from_gui": True}
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
self.log_text.config(state=tk.NORMAL)
|
self.log_text.config(state=tk.NORMAL)
|
||||||
self._append_message(text, normalized_level)
|
self._append_message(text, normalized_level)
|
||||||
self.log_text.see(tk.END)
|
self.log_text.see(tk.END)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import ttkbootstrap as ttk
|
import ttkbootstrap as ttk
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import messagebox, filedialog
|
from tkinter import messagebox, filedialog
|
||||||
import sys
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
@@ -23,6 +22,7 @@ from app.views.panels import cct_panel as _ccp
|
|||||||
from app.views.panels import main_layout as _main
|
from app.views.panels import main_layout as _main
|
||||||
from app.views.panels import ai_image_panel as _aip
|
from app.views.panels import ai_image_panel as _aip
|
||||||
from app.views import panel_manager as PM
|
from app.views import panel_manager as PM
|
||||||
|
from app.logging_setup import setup_logging, attach_gui_handler
|
||||||
|
|
||||||
# Step 0/1 重构:资源工具和纯算法已迁移到 app/ 包,这里重新导入以保持
|
# Step 0/1 重构:资源工具和纯算法已迁移到 app/ 包,这里重新导入以保持
|
||||||
# 对原函数名/方法名的向后兼容(老代码内部仍用 self.calculate_* 调用)。
|
# 对原函数名/方法名的向后兼容(老代码内部仍用 self.calculate_* 调用)。
|
||||||
@@ -877,17 +877,13 @@ class PQAutomationApp:
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
try:
|
try:
|
||||||
# 全局日志:默认 INFO 输出到 stderr,便于排查 AI 接口等关键事件
|
setup_logging()
|
||||||
import logging as _logging
|
|
||||||
if not _logging.getLogger().handlers:
|
|
||||||
_logging.basicConfig(
|
|
||||||
level=_logging.INFO,
|
|
||||||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
||||||
datefmt="%H:%M:%S",
|
|
||||||
)
|
|
||||||
# root = tk.Tk()
|
# root = tk.Tk()
|
||||||
root = ttk.Window(themename="yeti")
|
root = ttk.Window(themename="yeti")
|
||||||
app = PQAutomationApp(root)
|
app = PQAutomationApp(root)
|
||||||
|
# GUI 创建完成后,把 logging 记录同步到日志面板
|
||||||
|
if hasattr(app, "log_gui"):
|
||||||
|
attach_gui_handler(app.log_gui)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("程序发生错误:", e)
|
print("程序发生错误:", e)
|
||||||
|
|||||||
@@ -32,13 +32,7 @@
|
|||||||
"timing": "DMT 1920x 1080 @ 60Hz",
|
"timing": "DMT 1920x 1080 @ 60Hz",
|
||||||
"color_format": "RGB",
|
"color_format": "RGB",
|
||||||
"bpc": 8,
|
"bpc": 8,
|
||||||
"colorimetry": "sRGB",
|
"colorimetry": "sRGB"
|
||||||
"cct_params": {
|
|
||||||
"x_ideal": 0.3127,
|
|
||||||
"x_tolerance": 0.003,
|
|
||||||
"y_ideal": 0.329,
|
|
||||||
"y_tolerance": 0.003
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"hdr_movie": {
|
"hdr_movie": {
|
||||||
"name": "HDR Movie测试",
|
"name": "HDR Movie测试",
|
||||||
@@ -52,17 +46,11 @@
|
|||||||
"timing": "DMT 1920x 1080 @ 60Hz",
|
"timing": "DMT 1920x 1080 @ 60Hz",
|
||||||
"color_format": "RGB",
|
"color_format": "RGB",
|
||||||
"bpc": 8,
|
"bpc": 8,
|
||||||
"colorimetry": "sRGB",
|
"colorimetry": "sRGB"
|
||||||
"cct_params": {
|
|
||||||
"x_ideal": 0.3127,
|
|
||||||
"x_tolerance": 0.003,
|
|
||||||
"y_ideal": 0.329,
|
|
||||||
"y_tolerance": 0.003
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"device_config": {
|
"device_config": {
|
||||||
"ca_com": "COM3",
|
"ca_com": "COM1",
|
||||||
"ucd_list": "0: UCD-323 [2128C209]",
|
"ucd_list": "0: UCD-323 [2128C209]",
|
||||||
"ca_channel": "0"
|
"ca_channel": "0"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user