Compare commits
15 Commits
a2682dc040
...
972ef57337
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
972ef57337 | ||
|
|
12b8555592 | ||
|
|
c893aa6511 | ||
|
|
3cfb04f85a | ||
|
|
eb47ab4fe6 | ||
|
|
7236912a53 | ||
|
|
a1872a7a33 | ||
|
|
45bb0a4d41 | ||
|
|
371d959646 | ||
|
|
22aa782648 | ||
|
|
5c36736141 | ||
|
|
6dc4d0699a | ||
|
|
1678d67cfa | ||
|
|
b7a8a86e53 | ||
|
|
bb04bd8fa5 |
65
.dockerignore
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Virtual Environment
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Git
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
Dockerfile*
|
||||||
|
docker-compose*
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
README.md
|
||||||
|
*.md
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
.cache/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
15
.env
@@ -1,13 +1,14 @@
|
|||||||
# 环境变量配置文件
|
# 环境变量配置文件
|
||||||
|
|
||||||
# 数据库配置
|
# 数据库配置
|
||||||
DATABASE_URL=postgresql://luna:123luna@121.43.104.161:6432/luna
|
# DATABASE_URL=postgresql://luna:123luna@121.43.104.161:6432/luna
|
||||||
|
DATABASE_URL=postgresql://luna:123luna@6.6.6.66:5432/luna
|
||||||
|
|
||||||
# MQTT配置
|
# MQTT配置
|
||||||
MQTT_BROKER_HOST=localhost
|
MQTT_BROKER_HOST=luna-mqtt
|
||||||
MQTT_BROKER_PORT=1883
|
MQTT_BROKER_PORT=1883
|
||||||
# MQTT_USERNAME=
|
MQTT_USERNAME=luna2025
|
||||||
# MQTT_PASSWORD=
|
MQTT_PASSWORD=123luna2021
|
||||||
|
|
||||||
# 应用配置
|
# 应用配置
|
||||||
APP_NAME=墨水屏桌面屏幕系统
|
APP_NAME=墨水屏桌面屏幕系统
|
||||||
@@ -25,4 +26,8 @@ INK_HEIGHT=300
|
|||||||
# 安全配置
|
# 安全配置
|
||||||
SECRET_KEY=123tangledup-ai
|
SECRET_KEY=123tangledup-ai
|
||||||
ALGORITHM=HS256
|
ALGORITHM=HS256
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
||||||
|
|
||||||
|
# 管理员配置
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=123456
|
||||||
32
.env.docker
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# 环境变量配置文件
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
# DATABASE_URL=postgresql://luna:123luna@121.43.104.161:6432/luna
|
||||||
|
DATABASE_URL=postgresql://luna:123luna@6.6.6.66:5432/luna
|
||||||
|
# MQTT配置
|
||||||
|
MQTT_BROKER_HOST=luna-mqtt
|
||||||
|
MQTT_BROKER_PORT=1883
|
||||||
|
MQTT_USERNAME=luna2025
|
||||||
|
MQTT_PASSWORD=123luna2021
|
||||||
|
|
||||||
|
# 应用配置
|
||||||
|
APP_NAME=墨水屏桌面屏幕系统
|
||||||
|
DEBUG=false
|
||||||
|
|
||||||
|
# 文件存储配置
|
||||||
|
STATIC_DIR=static
|
||||||
|
UPLOAD_DIR=static/uploads
|
||||||
|
PROCESSED_DIR=static/processed
|
||||||
|
|
||||||
|
# 墨水屏配置
|
||||||
|
INK_WIDTH=400
|
||||||
|
INK_HEIGHT=300
|
||||||
|
|
||||||
|
# 安全配置
|
||||||
|
SECRET_KEY=123tangledup-ai
|
||||||
|
ALGORITHM=HS256
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
||||||
|
|
||||||
|
# 管理员配置
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=123456
|
||||||
18
.env.example
@@ -1,13 +1,13 @@
|
|||||||
# 环境变量配置文件
|
# 环境变量配置文件
|
||||||
|
|
||||||
# 数据库配置
|
# 数据库配置
|
||||||
DATABASE_URL=postgresql://luna:123luna@121.43.104.161:6432/luna_ink
|
# DATABASE_URL=postgresql://luna:123luna@121.43.104.161:6432/luna
|
||||||
|
DATABASE_URL=postgresql://luna:123luna@6.6.6.66:5432/luna
|
||||||
# MQTT配置
|
# MQTT配置
|
||||||
MQTT_BROKER_HOST=localhost
|
MQTT_BROKER_HOST=luna-mqtt
|
||||||
MQTT_BROKER_PORT=1883
|
MQTT_BROKER_PORT=1883
|
||||||
# MQTT_USERNAME=
|
MQTT_USERNAME=luna2025
|
||||||
# MQTT_PASSWORD=
|
MQTT_PASSWORD=123luna2021
|
||||||
|
|
||||||
# 应用配置
|
# 应用配置
|
||||||
APP_NAME=墨水屏桌面屏幕系统
|
APP_NAME=墨水屏桌面屏幕系统
|
||||||
@@ -23,6 +23,10 @@ INK_WIDTH=400
|
|||||||
INK_HEIGHT=300
|
INK_HEIGHT=300
|
||||||
|
|
||||||
# 安全配置
|
# 安全配置
|
||||||
SECRET_KEY=your-secret-key-change-in-production
|
SECRET_KEY=123tangledup-ai
|
||||||
ALGORITHM=HS256
|
ALGORITHM=HS256
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
||||||
|
|
||||||
|
# 管理员配置
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=123456
|
||||||
39
Dockerfile
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# 使用官方Python基础镜像,配置国内镜像源
|
||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 设置环境变量
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
PIP_NO_CACHE_DIR=1 \
|
||||||
|
PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||||
|
|
||||||
|
# 使用国内pip镜像源
|
||||||
|
# RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ && \
|
||||||
|
# pip config set global.trusted-host mirrors.aliyun.com
|
||||||
|
|
||||||
|
# 使用国内apt镜像源
|
||||||
|
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
gcc \
|
||||||
|
libpq-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 复制requirements文件并安装Python依赖
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# 复制项目文件
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# 创建必要的目录
|
||||||
|
RUN mkdir -p static/uploads static/processed
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 9999
|
||||||
|
|
||||||
|
# 启动命令
|
||||||
|
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8199"]
|
||||||
58
README-Docker.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Docker 部署指南
|
||||||
|
|
||||||
|
本项目已配置 Docker 和 Docker Compose,可以一键部署整个应用环境。
|
||||||
|
|
||||||
|
## 快速启动
|
||||||
|
|
||||||
|
1. 确保已安装 Docker 和 Docker Compose
|
||||||
|
2. 在项目根目录执行以下命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建并启动所有服务
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 查看服务状态
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# 查看应用日志
|
||||||
|
docker-compose logs -f luna-app
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 访问应用:
|
||||||
|
- API 文档:http://localhost:9999/docs
|
||||||
|
- 管理后台:http://localhost:9999/admin
|
||||||
|
|
||||||
|
## 服务说明
|
||||||
|
|
||||||
|
- **luna-app**: 主应用服务,运行在 9999 端口
|
||||||
|
- **luna-mqtt**: MQTT 服务,运行在 1883 端口
|
||||||
|
|
||||||
|
注意:本项目使用外部PostgreSQL数据库,数据库地址已在.env.docker文件中配置。
|
||||||
|
|
||||||
|
## 停止服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 停止所有服务
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# 停止并删除数据卷(注意:这将删除所有数据)
|
||||||
|
docker-compose down -v
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 首次启动可能需要几分钟等待数据库初始化完成
|
||||||
|
2. 应用配置通过.env.docker文件传递,该文件已配置为使用Docker内部服务名
|
||||||
|
3. 静态文件通过 volume 挂载,可以直接在宿主机修改
|
||||||
|
4. 数据持久化通过 Docker volumes 实现
|
||||||
|
5. 如需修改配置,请编辑.env.docker文件,而不是.env文件
|
||||||
|
|
||||||
|
## 国内镜像源
|
||||||
|
|
||||||
|
本项目已配置使用国内镜像源,包括:
|
||||||
|
- Python 基础镜像:阿里云镜像
|
||||||
|
- pip 包源:阿里云 pip 源
|
||||||
|
- PostgreSQL:阿里云镜像
|
||||||
|
- Mosquitto:阿里云镜像
|
||||||
|
|
||||||
|
这样可以大大提高在国内的构建和拉取速度。
|
||||||
BIN
__pycache__/admin_routes.cpython-313.pyc
Normal file
BIN
__pycache__/auth.cpython-312.pyc
Normal file
BIN
__pycache__/auth.cpython-313.pyc
Normal file
BIN
__pycache__/image_processor.cpython-313.pyc
Normal file
BIN
__pycache__/models.cpython-313.pyc
Normal file
BIN
__pycache__/mqtt_manager.cpython-313.pyc
Normal file
BIN
__pycache__/schemas.cpython-313.pyc
Normal file
349
admin_routes.py
@@ -6,10 +6,12 @@ from typing import Optional, List
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
|
from datetime import datetime
|
||||||
|
from config import settings
|
||||||
|
|
||||||
from database import get_db
|
from database import get_db
|
||||||
from models import Device as DeviceModel, Content as ContentModel
|
from models import Device as DeviceModel, Content as ContentModel, Todo as TodoModel
|
||||||
from schemas import DeviceCreate, ContentCreate
|
from schemas import DeviceCreate, ContentCreate, TodoCreate, TodoUpdate
|
||||||
from image_processor import image_processor
|
from image_processor import image_processor
|
||||||
from mqtt_manager import mqtt_manager
|
from mqtt_manager import mqtt_manager
|
||||||
|
|
||||||
@@ -19,6 +21,68 @@ templates = Jinja2Templates(directory="templates")
|
|||||||
# 创建管理后台路由
|
# 创建管理后台路由
|
||||||
admin_router = APIRouter()
|
admin_router = APIRouter()
|
||||||
|
|
||||||
|
# 登录页面
|
||||||
|
@admin_router.get("/login", response_class=HTMLResponse)
|
||||||
|
async def login_page(request: Request, next: Optional[str] = None):
|
||||||
|
"""
|
||||||
|
管理员登录页面
|
||||||
|
"""
|
||||||
|
# 如果已经登录,重定向到首页
|
||||||
|
if request.session.get("authenticated"):
|
||||||
|
return RedirectResponse(url=next or "/admin/", status_code=303)
|
||||||
|
|
||||||
|
return templates.TemplateResponse("admin/login.html", {
|
||||||
|
"request": request,
|
||||||
|
"next": next
|
||||||
|
})
|
||||||
|
|
||||||
|
# 登录处理
|
||||||
|
@admin_router.post("/login", response_class=HTMLResponse)
|
||||||
|
async def login_submit(
|
||||||
|
request: Request,
|
||||||
|
username: str = Form(...),
|
||||||
|
password: str = Form(...),
|
||||||
|
remember: Optional[bool] = Form(False),
|
||||||
|
next: Optional[str] = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
处理管理员登录
|
||||||
|
"""
|
||||||
|
# 验证用户名和密码
|
||||||
|
# 这里使用配置文件中的设置,实际项目中应该使用数据库存储用户信息
|
||||||
|
if username == settings.admin_username and password == settings.admin_password:
|
||||||
|
# 设置会话
|
||||||
|
request.session["authenticated"] = True
|
||||||
|
request.session["username"] = username
|
||||||
|
|
||||||
|
# 设置会话过期时间
|
||||||
|
if remember:
|
||||||
|
request.session["expire_at_browser_close"] = False
|
||||||
|
else:
|
||||||
|
request.session["expire_at_browser_close"] = True
|
||||||
|
|
||||||
|
# 重定向到原始请求的页面或首页
|
||||||
|
return RedirectResponse(url=next or "/admin/", status_code=303)
|
||||||
|
else:
|
||||||
|
# 登录失败,返回错误信息
|
||||||
|
return templates.TemplateResponse("admin/login.html", {
|
||||||
|
"request": request,
|
||||||
|
"next": next,
|
||||||
|
"error": "用户名或密码错误"
|
||||||
|
})
|
||||||
|
|
||||||
|
# 登出
|
||||||
|
@admin_router.get("/logout", response_class=HTMLResponse)
|
||||||
|
async def logout(request: Request):
|
||||||
|
"""
|
||||||
|
管理员登出
|
||||||
|
"""
|
||||||
|
# 清除会话
|
||||||
|
request.session.clear()
|
||||||
|
|
||||||
|
# 重定向到登录页面
|
||||||
|
return RedirectResponse(url="/admin/login", status_code=303)
|
||||||
|
|
||||||
# 管理后台路由
|
# 管理后台路由
|
||||||
@admin_router.get("/", response_class=HTMLResponse)
|
@admin_router.get("/", response_class=HTMLResponse)
|
||||||
async def admin_dashboard(request: Request, db: Session = Depends(get_db)):
|
async def admin_dashboard(request: Request, db: Session = Depends(get_db)):
|
||||||
@@ -31,22 +95,33 @@ async def admin_dashboard(request: Request, db: Session = Depends(get_db)):
|
|||||||
|
|
||||||
# 获取内容数量
|
# 获取内容数量
|
||||||
content_count = db.query(ContentModel).count()
|
content_count = db.query(ContentModel).count()
|
||||||
active_content_count = db.query(ContentModel).filter(ContentModel.is_active == True).count()
|
|
||||||
|
# 获取待办事项数量
|
||||||
|
todo_count = db.query(TodoModel).count()
|
||||||
|
completed_todo_count = db.query(TodoModel).filter(TodoModel.is_completed == True).count()
|
||||||
|
pending_todo_count = todo_count - completed_todo_count
|
||||||
|
|
||||||
# 获取最近上线的设备
|
# 获取最近上线的设备
|
||||||
recent_devices = db.query(DeviceModel).order_by(DeviceModel.last_online.desc()).limit(5).all()
|
recent_devices = db.query(DeviceModel).order_by(DeviceModel.last_online.desc()).limit(5).all()
|
||||||
|
|
||||||
# 获取最近创建的内容
|
# 获取最近创建的待办事项
|
||||||
recent_contents = db.query(ContentModel).order_by(ContentModel.created_at.desc()).limit(5).all()
|
recent_todos_query = db.query(TodoModel, DeviceModel).join(
|
||||||
|
DeviceModel, TodoModel.device_id == DeviceModel.device_id
|
||||||
|
).order_by(TodoModel.created_at.desc()).limit(5).all()
|
||||||
|
|
||||||
|
# 转换为包含todo和device的对象列表
|
||||||
|
recent_todos = [{"todo": todo, "device": device} for todo, device in recent_todos_query]
|
||||||
|
|
||||||
return templates.TemplateResponse("admin/dashboard.html", {
|
return templates.TemplateResponse("admin/dashboard.html", {
|
||||||
"request": request,
|
"request": request,
|
||||||
"device_count": device_count,
|
"device_count": device_count,
|
||||||
"active_device_count": active_device_count,
|
"active_device_count": active_device_count,
|
||||||
"content_count": content_count,
|
"content_count": content_count,
|
||||||
"active_content_count": active_content_count,
|
"todo_count": todo_count,
|
||||||
|
"completed_todo_count": completed_todo_count,
|
||||||
|
"pending_todo_count": pending_todo_count,
|
||||||
"recent_devices": recent_devices,
|
"recent_devices": recent_devices,
|
||||||
"recent_contents": recent_contents
|
"recent_todos": recent_todos
|
||||||
})
|
})
|
||||||
|
|
||||||
@admin_router.get("/devices", response_class=HTMLResponse)
|
@admin_router.get("/devices", response_class=HTMLResponse)
|
||||||
@@ -341,4 +416,262 @@ async def upload_image(request: Request, db: Session = Depends(get_db)):
|
|||||||
"request": request,
|
"request": request,
|
||||||
"devices": devices,
|
"devices": devices,
|
||||||
"error": f"图片处理失败: {str(e)}"
|
"error": f"图片处理失败: {str(e)}"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# 待办事项管理路由
|
||||||
|
@admin_router.get("/todos", response_class=HTMLResponse)
|
||||||
|
async def todos_list(request: Request, device_id: Optional[str] = None, db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
待办事项列表页面
|
||||||
|
"""
|
||||||
|
if device_id:
|
||||||
|
# 获取特定设备的待办事项
|
||||||
|
device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first()
|
||||||
|
if not device:
|
||||||
|
raise HTTPException(status_code=404, detail="设备不存在")
|
||||||
|
|
||||||
|
todos = db.query(TodoModel).filter(TodoModel.device_id == device_id).order_by(TodoModel.created_at.desc()).all()
|
||||||
|
return templates.TemplateResponse("admin/todos.html", {
|
||||||
|
"request": request,
|
||||||
|
"todos": todos,
|
||||||
|
"device": device,
|
||||||
|
"filtered": True
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# 获取所有待办事项
|
||||||
|
todos = db.query(TodoModel).order_by(TodoModel.created_at.desc()).all()
|
||||||
|
devices = db.query(DeviceModel).all()
|
||||||
|
|
||||||
|
# 为每个待办事项添加设备信息
|
||||||
|
todo_list = []
|
||||||
|
for todo in todos:
|
||||||
|
device = db.query(DeviceModel).filter(DeviceModel.device_id == todo.device_id).first()
|
||||||
|
todo_list.append({
|
||||||
|
"todo": todo,
|
||||||
|
"device": device
|
||||||
|
})
|
||||||
|
|
||||||
|
return templates.TemplateResponse("admin/todos.html", {
|
||||||
|
"request": request,
|
||||||
|
"todo_list": todo_list,
|
||||||
|
"devices": devices,
|
||||||
|
"filtered": False
|
||||||
|
})
|
||||||
|
|
||||||
|
@admin_router.get("/todos/add", response_class=HTMLResponse)
|
||||||
|
@admin_router.post("/todos/add", response_class=HTMLResponse)
|
||||||
|
async def add_todo(request: Request, device_id: Optional[str] = None, db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
添加待办事项页面和处理
|
||||||
|
"""
|
||||||
|
if request.method == "GET":
|
||||||
|
devices = db.query(DeviceModel).filter(DeviceModel.is_active == True).all()
|
||||||
|
return templates.TemplateResponse("admin/todo_add.html", {
|
||||||
|
"request": request,
|
||||||
|
"devices": devices,
|
||||||
|
"selected_device": device_id
|
||||||
|
})
|
||||||
|
|
||||||
|
# 处理POST请求
|
||||||
|
form = await request.form()
|
||||||
|
device_id = form.get("device_id")
|
||||||
|
title = form.get("title")
|
||||||
|
description = form.get("description")
|
||||||
|
due_date_str = form.get("due_date")
|
||||||
|
|
||||||
|
# 检查设备是否存在
|
||||||
|
device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first()
|
||||||
|
if not device:
|
||||||
|
devices = db.query(DeviceModel).filter(DeviceModel.is_active == True).all()
|
||||||
|
return templates.TemplateResponse("admin/todo_add.html", {
|
||||||
|
"request": request,
|
||||||
|
"devices": devices,
|
||||||
|
"error": "设备不存在"
|
||||||
|
})
|
||||||
|
|
||||||
|
# 处理截止日期
|
||||||
|
due_date = None
|
||||||
|
if due_date_str:
|
||||||
|
try:
|
||||||
|
from datetime import datetime
|
||||||
|
due_date = datetime.strptime(due_date_str, "%Y-%m-%dT%H:%M")
|
||||||
|
except ValueError:
|
||||||
|
devices = db.query(DeviceModel).filter(DeviceModel.is_active == True).all()
|
||||||
|
return templates.TemplateResponse("admin/todo_add.html", {
|
||||||
|
"request": request,
|
||||||
|
"devices": devices,
|
||||||
|
"error": "截止日期格式不正确"
|
||||||
|
})
|
||||||
|
|
||||||
|
# 创建新的待办事项
|
||||||
|
new_todo = TodoModel(
|
||||||
|
title=title,
|
||||||
|
description=description,
|
||||||
|
device_id=device_id,
|
||||||
|
due_date=due_date
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(new_todo)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# 发送MQTT通知给设备
|
||||||
|
mqtt_manager.send_todo_command(device_id, "create", {
|
||||||
|
"id": new_todo.id,
|
||||||
|
"title": title,
|
||||||
|
"description": description,
|
||||||
|
"due_date": due_date.isoformat() if due_date else None
|
||||||
|
})
|
||||||
|
|
||||||
|
return RedirectResponse(url="/admin/todos", status_code=303)
|
||||||
|
|
||||||
|
@admin_router.get("/todos/{todo_id}", response_class=HTMLResponse)
|
||||||
|
async def todo_detail(request: Request, todo_id: int, db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
待办事项详情页面
|
||||||
|
"""
|
||||||
|
todo = db.query(TodoModel).filter(TodoModel.id == todo_id).first()
|
||||||
|
if not todo:
|
||||||
|
raise HTTPException(status_code=404, detail="待办事项不存在")
|
||||||
|
|
||||||
|
# 获取设备信息
|
||||||
|
device = db.query(DeviceModel).filter(DeviceModel.device_id == todo.device_id).first()
|
||||||
|
|
||||||
|
return templates.TemplateResponse("admin/todo_detail.html", {
|
||||||
|
"request": request,
|
||||||
|
"todo": todo,
|
||||||
|
"device": device
|
||||||
|
})
|
||||||
|
|
||||||
|
@admin_router.post("/todos/{todo_id}/toggle", response_class=HTMLResponse)
|
||||||
|
async def toggle_todo_status(todo_id: int, db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
切换待办事项完成状态
|
||||||
|
"""
|
||||||
|
todo = db.query(TodoModel).filter(TodoModel.id == todo_id).first()
|
||||||
|
if not todo:
|
||||||
|
raise HTTPException(status_code=404, detail="待办事项不存在")
|
||||||
|
|
||||||
|
# 切换状态
|
||||||
|
todo.is_completed = not todo.is_completed
|
||||||
|
if todo.is_completed:
|
||||||
|
todo.completed_at = datetime.utcnow()
|
||||||
|
else:
|
||||||
|
todo.completed_at = None
|
||||||
|
|
||||||
|
todo.updated_at = datetime.utcnow()
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# 发送MQTT通知给设备
|
||||||
|
mqtt_manager.send_todo_command(todo.device_id, "update", {
|
||||||
|
"id": todo.id,
|
||||||
|
"is_completed": todo.is_completed,
|
||||||
|
"completed_at": todo.completed_at.isoformat() if todo.completed_at else None
|
||||||
|
})
|
||||||
|
|
||||||
|
return RedirectResponse(url=f"/admin/todos/{todo_id}", status_code=303)
|
||||||
|
|
||||||
|
@admin_router.get("/todos/{todo_id}/edit", response_class=HTMLResponse)
|
||||||
|
async def edit_todo_page(request: Request, todo_id: int, db: Session = Depends(get_db)):
|
||||||
|
todo = db.query(TodoModel).filter(TodoModel.id == todo_id).first()
|
||||||
|
if not todo:
|
||||||
|
raise HTTPException(status_code=404, detail="待办事项不存在")
|
||||||
|
|
||||||
|
devices = db.query(DeviceModel).all()
|
||||||
|
|
||||||
|
return templates.TemplateResponse("admin/todo_edit.html", {
|
||||||
|
"request": request,
|
||||||
|
"todo": todo,
|
||||||
|
"devices": devices
|
||||||
|
})
|
||||||
|
|
||||||
|
@admin_router.post("/todos/{todo_id}/edit")
|
||||||
|
async def edit_todo(
|
||||||
|
request: Request,
|
||||||
|
todo_id: int,
|
||||||
|
title: str = Form(...),
|
||||||
|
description: str = Form(""),
|
||||||
|
device_id: str = Form(...),
|
||||||
|
due_date: Optional[str] = Form(None),
|
||||||
|
is_completed: bool = Form(False),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
todo = db.query(TodoModel).filter(TodoModel.id == todo_id).first()
|
||||||
|
if not todo:
|
||||||
|
raise HTTPException(status_code=404, detail="待办事项不存在")
|
||||||
|
|
||||||
|
device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first()
|
||||||
|
if not device:
|
||||||
|
raise HTTPException(status_code=404, detail="设备不存在")
|
||||||
|
|
||||||
|
# 更新待办事项
|
||||||
|
todo.title = title
|
||||||
|
todo.description = description if description else None
|
||||||
|
todo.device_id = device_id
|
||||||
|
todo.due_date = datetime.fromisoformat(due_date) if due_date else None
|
||||||
|
|
||||||
|
# 检查完成状态变化
|
||||||
|
was_completed = todo.is_completed
|
||||||
|
todo.is_completed = is_completed
|
||||||
|
|
||||||
|
# 如果从未完成变为已完成,设置完成时间
|
||||||
|
if not was_completed and is_completed:
|
||||||
|
todo.completed_at = datetime.utcnow()
|
||||||
|
# 如果从已完成变为未完成,清除完成时间
|
||||||
|
elif was_completed and not is_completed:
|
||||||
|
todo.completed_at = None
|
||||||
|
|
||||||
|
todo.updated_at = datetime.utcnow()
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# 发送MQTT通知到设备
|
||||||
|
if hasattr(request.app.state, 'mqtt_manager') and request.app.state.mqtt_manager:
|
||||||
|
try:
|
||||||
|
await request.app.state.mqtt_manager.send_todo_command(
|
||||||
|
device_id=device_id,
|
||||||
|
action="update",
|
||||||
|
todo_data={
|
||||||
|
"id": todo.id,
|
||||||
|
"title": todo.title,
|
||||||
|
"description": todo.description,
|
||||||
|
"due_date": todo.due_date.isoformat() if todo.due_date else None,
|
||||||
|
"is_completed": todo.is_completed
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"发送待办事项更新MQTT消息失败: {e}")
|
||||||
|
|
||||||
|
return RedirectResponse(url=f"/admin/todos/{todo_id}", status_code=303)
|
||||||
|
|
||||||
|
@admin_router.post("/todos/{todo_id}/delete")
|
||||||
|
async def delete_todo(
|
||||||
|
request: Request,
|
||||||
|
todo_id: int,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
删除待办事项
|
||||||
|
"""
|
||||||
|
todo = db.query(TodoModel).filter(TodoModel.id == todo_id).first()
|
||||||
|
if not todo:
|
||||||
|
raise HTTPException(status_code=404, detail="待办事项不存在")
|
||||||
|
|
||||||
|
device_id = todo.device_id
|
||||||
|
|
||||||
|
# 发送MQTT通知到设备
|
||||||
|
if hasattr(request.app.state, 'mqtt_manager') and request.app.state.mqtt_manager:
|
||||||
|
try:
|
||||||
|
await request.app.state.mqtt_manager.send_todo_command(
|
||||||
|
device_id=device_id,
|
||||||
|
action="delete",
|
||||||
|
todo_data={
|
||||||
|
"id": todo.id
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"发送待办事项删除MQTT消息失败: {e}")
|
||||||
|
|
||||||
|
db.delete(todo)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return RedirectResponse(url=f"/admin/todos?device_id={device_id}", status_code=303)
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from api import devices, contents
|
from api import devices, contents, todos
|
||||||
|
|
||||||
api_router = APIRouter()
|
api_router = APIRouter()
|
||||||
|
|
||||||
# 注册所有路由
|
# 注册所有路由,并添加全局安全要求
|
||||||
api_router.include_router(devices.router)
|
api_router.include_router(devices.router, prefix="/devices")
|
||||||
api_router.include_router(contents.router)
|
api_router.include_router(contents.router, prefix="/contents")
|
||||||
|
api_router.include_router(todos.router, prefix="/todos")
|
||||||
BIN
api/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
api/__pycache__/contents.cpython-313.pyc
Normal file
BIN
api/__pycache__/devices.cpython-313.pyc
Normal file
BIN
api/__pycache__/todos.cpython-312.pyc
Normal file
BIN
api/__pycache__/todos.cpython-313.pyc
Normal file
348
api/contents.py
@@ -1,9 +1,12 @@
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, status, Query, File, UploadFile
|
from fastapi import APIRouter, Depends, HTTPException, status, Query, File, UploadFile, Security, Response
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
from database import get_db
|
from database import get_db
|
||||||
from schemas import Content as ContentSchema, ContentCreate, ContentUpdate, ContentResponse
|
from schemas import Content as ContentSchema, ContentCreate, ContentUpdate, ContentResponse
|
||||||
@@ -11,13 +14,13 @@ from models import Content as ContentModel, Device as DeviceModel
|
|||||||
from mqtt_manager import mqtt_manager
|
from mqtt_manager import mqtt_manager
|
||||||
from image_processor import image_processor
|
from image_processor import image_processor
|
||||||
from config import settings
|
from config import settings
|
||||||
|
from auth import get_api_key
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
prefix="/api",
|
|
||||||
tags=["contents"]
|
tags=["contents"]
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.post("/devices/{device_id}/content", response_model=ContentSchema, status_code=status.HTTP_201_CREATED)
|
@router.post("/devices/{device_id}/content", response_model=ContentSchema, status_code=status.HTTP_201_CREATED, dependencies=[Depends(get_api_key)])
|
||||||
async def create_content(
|
async def create_content(
|
||||||
device_id: str,
|
device_id: str,
|
||||||
content: ContentCreate,
|
content: ContentCreate,
|
||||||
@@ -60,7 +63,7 @@ async def create_content(
|
|||||||
|
|
||||||
return db_content
|
return db_content
|
||||||
|
|
||||||
@router.get("/devices/{device_id}/content", response_model=List[ContentSchema])
|
@router.get("/devices/{device_id}/content", response_model=List[ContentSchema], dependencies=[Depends(get_api_key)])
|
||||||
async def list_content(
|
async def list_content(
|
||||||
device_id: str,
|
device_id: str,
|
||||||
skip: int = 0,
|
skip: int = 0,
|
||||||
@@ -87,7 +90,7 @@ async def list_content(
|
|||||||
contents = query.order_by(ContentModel.version.desc()).offset(skip).limit(limit).all()
|
contents = query.order_by(ContentModel.version.desc()).offset(skip).limit(limit).all()
|
||||||
return contents
|
return contents
|
||||||
|
|
||||||
@router.get("/devices/{device_id}/content/{version}", response_model=ContentResponse)
|
@router.get("/devices/{device_id}/content/{version}", response_model=ContentResponse, dependencies=[Depends(get_api_key)])
|
||||||
async def get_content(
|
async def get_content(
|
||||||
device_id: str,
|
device_id: str,
|
||||||
version: int,
|
version: int,
|
||||||
@@ -141,7 +144,7 @@ async def get_content(
|
|||||||
created_at=content.created_at
|
created_at=content.created_at
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.put("/devices/{device_id}/content/{version}", response_model=ContentSchema)
|
@router.put("/devices/{device_id}/content/{version}", response_model=ContentSchema, dependencies=[Depends(get_api_key)])
|
||||||
async def update_content(
|
async def update_content(
|
||||||
device_id: str,
|
device_id: str,
|
||||||
version: int,
|
version: int,
|
||||||
@@ -176,7 +179,7 @@ async def update_content(
|
|||||||
|
|
||||||
return content
|
return content
|
||||||
|
|
||||||
@router.delete("/devices/{device_id}/content/{version}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/devices/{device_id}/content/{version}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(get_api_key)])
|
||||||
async def delete_content(
|
async def delete_content(
|
||||||
device_id: str,
|
device_id: str,
|
||||||
version: int,
|
version: int,
|
||||||
@@ -250,7 +253,7 @@ async def get_latest_content(device_id: str, db: Session = Depends(get_db)):
|
|||||||
created_at=content.created_at
|
created_at=content.created_at
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.post("/upload")
|
@router.post("/upload", dependencies=[Depends(get_api_key)])
|
||||||
async def upload_image(
|
async def upload_image(
|
||||||
device_id: str = Query(..., description="设备ID"),
|
device_id: str = Query(..., description="设备ID"),
|
||||||
version: Optional[int] = Query(None, description="内容版本,如果提供则更新指定版本"),
|
version: Optional[int] = Query(None, description="内容版本,如果提供则更新指定版本"),
|
||||||
@@ -336,4 +339,333 @@ async def upload_image(
|
|||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
detail=f"图片处理失败: {str(e)}"
|
detail=f"图片处理失败: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def convert_to_binary_data(image_path: str, width: int = 400, height: int = 300, invert: bool = False, rotate: bool = False, dither: bool = True) -> bytes:
|
||||||
|
"""
|
||||||
|
使用image_converter.py工具将图片转换为二进制数据
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_path: 图片路径
|
||||||
|
width: 目标宽度
|
||||||
|
height: 目标高度
|
||||||
|
invert: 是否反转颜色
|
||||||
|
rotate: 是否旋转90度
|
||||||
|
dither: 是否使用抖动算法
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
二进制数据
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 动态导入image_converter模块 - 使用相对路径
|
||||||
|
import os
|
||||||
|
# 获取当前文件的目录
|
||||||
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
# 构建到tool目录的路径
|
||||||
|
tool_dir = os.path.join(current_dir, "..", "tool")
|
||||||
|
image_converter_path = os.path.join(tool_dir, "image_converter.py")
|
||||||
|
# 导入模块
|
||||||
|
spec = importlib.util.spec_from_file_location("image_converter", image_converter_path)
|
||||||
|
image_converter = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(image_converter)
|
||||||
|
|
||||||
|
# 创建临时文件
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.py', delete=False) as temp_file:
|
||||||
|
temp_path = temp_file.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 使用image_converter转换图片
|
||||||
|
image_converter.convert_image_to_epaper(
|
||||||
|
image_path,
|
||||||
|
temp_path,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
invert=invert,
|
||||||
|
rotate=rotate,
|
||||||
|
dither=dither
|
||||||
|
)
|
||||||
|
|
||||||
|
# 读取生成的Python文件并提取二进制数据
|
||||||
|
with open(temp_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# 解析二进制数据
|
||||||
|
start_idx = content.find("bytearray(b'") + len("bytearray(b'")
|
||||||
|
end_idx = content.find("')", start_idx)
|
||||||
|
|
||||||
|
# 提取并解析十六进制字符串
|
||||||
|
hex_str = content[start_idx:end_idx]
|
||||||
|
# 替换换行符和空格
|
||||||
|
hex_str = hex_str.replace("'\n b'", "")
|
||||||
|
|
||||||
|
# 转换为字节数组
|
||||||
|
binary_data = bytearray()
|
||||||
|
i = 0
|
||||||
|
while i < len(hex_str):
|
||||||
|
if hex_str[i] == '\\' and i + 1 < len(hex_str) and hex_str[i+1] == 'x':
|
||||||
|
# 提取十六进制值
|
||||||
|
hex_val = hex_str[i+2:i+4]
|
||||||
|
binary_data.append(int(hex_val, 16))
|
||||||
|
i += 4
|
||||||
|
else:
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return bytes(binary_data)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# 删除临时文件
|
||||||
|
if os.path.exists(temp_path):
|
||||||
|
os.unlink(temp_path)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"图片转换为二进制数据失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.get("/devices/{device_id}/content/latest/binary", dependencies=[Depends(get_api_key)])
|
||||||
|
async def get_latest_content_binary(
|
||||||
|
device_id: str,
|
||||||
|
invert: bool = Query(False, description="是否反转颜色"),
|
||||||
|
rotate: bool = Query(False, description="是否旋转90度"),
|
||||||
|
dither: bool = Query(True, description="是否使用抖动算法"),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
获取设备最新活跃内容的二进制数据,适用于墨水屏显示
|
||||||
|
"""
|
||||||
|
# 检查设备是否存在
|
||||||
|
device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first()
|
||||||
|
if not device:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="设备不存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取最新的活跃内容
|
||||||
|
content = db.query(ContentModel).filter(
|
||||||
|
ContentModel.device_id == device_id,
|
||||||
|
ContentModel.is_active == True
|
||||||
|
).order_by(ContentModel.version.desc()).first()
|
||||||
|
|
||||||
|
if not content:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="设备没有活跃内容"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not content.image_path:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="内容没有关联的图片"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 获取完整的图片路径
|
||||||
|
# 检查image_path是否已经包含static目录
|
||||||
|
if content.image_path.startswith('static/'):
|
||||||
|
# 已经包含static目录,直接使用
|
||||||
|
image_path = content.image_path
|
||||||
|
elif content.image_path.startswith('/'):
|
||||||
|
# 以/开头的完整路径,去掉开头的斜杠
|
||||||
|
image_path = content.image_path[1:]
|
||||||
|
else:
|
||||||
|
# 相对路径,添加static_dir前缀
|
||||||
|
image_path = os.path.join(settings.static_dir, content.image_path)
|
||||||
|
|
||||||
|
# 确保图片文件存在
|
||||||
|
if not os.path.exists(image_path):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="图片文件不存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 转换为二进制数据
|
||||||
|
binary_data = convert_to_binary_data(
|
||||||
|
image_path,
|
||||||
|
width=settings.ink_width,
|
||||||
|
height=settings.ink_height,
|
||||||
|
invert=invert,
|
||||||
|
rotate=rotate,
|
||||||
|
dither=dither
|
||||||
|
)
|
||||||
|
|
||||||
|
# 返回二进制数据
|
||||||
|
return Response(
|
||||||
|
content=binary_data,
|
||||||
|
media_type="application/octet-stream",
|
||||||
|
headers={
|
||||||
|
"Content-Disposition": f"attachment; filename={device_id}_latest.bin",
|
||||||
|
"Content-Length": str(len(binary_data))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"生成二进制数据失败: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
@router.get("/public/devices/{device_id}/content/latest/binary")
|
||||||
|
async def get_latest_content_binary_public(
|
||||||
|
device_id: str,
|
||||||
|
invert: bool = Query(False, description="是否反转颜色"),
|
||||||
|
rotate: bool = Query(False, description="是否旋转90度"),
|
||||||
|
dither: bool = Query(True, description="是否使用抖动算法"),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
获取设备最新活跃内容的二进制数据,适用于墨水屏显示(无需鉴权)
|
||||||
|
"""
|
||||||
|
# 检查设备是否存在
|
||||||
|
device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first()
|
||||||
|
if not device:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="设备不存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取最新的活跃内容
|
||||||
|
content = db.query(ContentModel).filter(
|
||||||
|
ContentModel.device_id == device_id,
|
||||||
|
ContentModel.is_active == True
|
||||||
|
).order_by(ContentModel.version.desc()).first()
|
||||||
|
|
||||||
|
if not content:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="设备没有活跃内容"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not content.image_path:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="内容没有关联的图片"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 获取完整的图片路径
|
||||||
|
# 检查image_path是否已经包含static目录
|
||||||
|
if content.image_path.startswith('static/'):
|
||||||
|
# 已经包含static目录,直接使用
|
||||||
|
image_path = content.image_path
|
||||||
|
elif content.image_path.startswith('/'):
|
||||||
|
# 以/开头的完整路径,去掉开头的斜杠
|
||||||
|
image_path = content.image_path[1:]
|
||||||
|
else:
|
||||||
|
# 相对路径,添加static_dir前缀
|
||||||
|
image_path = os.path.join(settings.static_dir, content.image_path)
|
||||||
|
|
||||||
|
# 确保图片文件存在
|
||||||
|
if not os.path.exists(image_path):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="图片文件不存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 转换为二进制数据
|
||||||
|
binary_data = convert_to_binary_data(
|
||||||
|
image_path,
|
||||||
|
width=settings.ink_width,
|
||||||
|
height=settings.ink_height,
|
||||||
|
invert=invert,
|
||||||
|
rotate=rotate,
|
||||||
|
dither=dither
|
||||||
|
)
|
||||||
|
|
||||||
|
# 返回二进制数据
|
||||||
|
return Response(
|
||||||
|
content=binary_data,
|
||||||
|
media_type="application/octet-stream",
|
||||||
|
headers={
|
||||||
|
"Content-Disposition": f"attachment; filename={device_id}_latest.bin",
|
||||||
|
"Content-Length": str(len(binary_data))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"生成二进制数据失败: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
@router.get("/devices/{device_id}/content/{version}/binary", dependencies=[Depends(get_api_key)])
|
||||||
|
async def get_content_binary(
|
||||||
|
device_id: str,
|
||||||
|
version: int,
|
||||||
|
invert: bool = Query(False, description="是否反转颜色"),
|
||||||
|
rotate: bool = Query(False, description="是否旋转90度"),
|
||||||
|
dither: bool = Query(True, description="是否使用抖动算法"),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
获取内容的二进制数据,适用于墨水屏显示
|
||||||
|
"""
|
||||||
|
# 检查设备是否存在
|
||||||
|
device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first()
|
||||||
|
if not device:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="设备不存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取内容
|
||||||
|
content = db.query(ContentModel).filter(
|
||||||
|
ContentModel.device_id == device_id,
|
||||||
|
ContentModel.version == version
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not content:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="内容不存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not content.image_path:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="内容没有关联的图片"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 获取完整的图片路径
|
||||||
|
# 检查image_path是否已经包含static目录
|
||||||
|
if content.image_path.startswith('static/'):
|
||||||
|
# 已经包含static目录,直接使用
|
||||||
|
image_path = content.image_path
|
||||||
|
elif content.image_path.startswith('/'):
|
||||||
|
# 以/开头的完整路径,去掉开头的斜杠
|
||||||
|
image_path = content.image_path[1:]
|
||||||
|
else:
|
||||||
|
# 相对路径,添加static_dir前缀
|
||||||
|
image_path = os.path.join(settings.static_dir, content.image_path)
|
||||||
|
|
||||||
|
# 确保图片文件存在
|
||||||
|
if not os.path.exists(image_path):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="图片文件不存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 转换为二进制数据
|
||||||
|
binary_data = convert_to_binary_data(
|
||||||
|
image_path,
|
||||||
|
width=settings.ink_width,
|
||||||
|
height=settings.ink_height,
|
||||||
|
invert=invert,
|
||||||
|
rotate=rotate,
|
||||||
|
dither=dither
|
||||||
|
)
|
||||||
|
|
||||||
|
# 返回二进制数据
|
||||||
|
return Response(
|
||||||
|
content=binary_data,
|
||||||
|
media_type="application/octet-stream",
|
||||||
|
headers={
|
||||||
|
"Content-Disposition": f"attachment; filename={device_id}_v{version}.bin",
|
||||||
|
"Content-Length": str(len(binary_data))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"生成二进制数据失败: {str(e)}"
|
||||||
)
|
)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status, Security
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -9,13 +9,13 @@ from schemas import Device as DeviceSchema, DeviceCreate, DeviceUpdate, Bootstra
|
|||||||
from models import Device as DeviceModel
|
from models import Device as DeviceModel
|
||||||
from database import Content as ContentModel
|
from database import Content as ContentModel
|
||||||
from mqtt_manager import mqtt_manager
|
from mqtt_manager import mqtt_manager
|
||||||
|
from auth import get_api_key
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
prefix="/api/devices",
|
|
||||||
tags=["devices"]
|
tags=["devices"]
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.post("/", response_model=DeviceSchema, status_code=status.HTTP_201_CREATED)
|
@router.post("/", response_model=DeviceSchema, status_code=status.HTTP_201_CREATED, dependencies=[Depends(get_api_key)])
|
||||||
async def create_device(device: DeviceCreate, db: Session = Depends(get_db)):
|
async def create_device(device: DeviceCreate, db: Session = Depends(get_db)):
|
||||||
"""
|
"""
|
||||||
注册新设备
|
注册新设备
|
||||||
@@ -46,7 +46,7 @@ async def create_device(device: DeviceCreate, db: Session = Depends(get_db)):
|
|||||||
|
|
||||||
return db_device
|
return db_device
|
||||||
|
|
||||||
@router.get("/", response_model=List[DeviceSchema])
|
@router.get("/", response_model=List[DeviceSchema], dependencies=[Depends(get_api_key)])
|
||||||
async def list_devices(
|
async def list_devices(
|
||||||
skip: int = 0,
|
skip: int = 0,
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
@@ -68,7 +68,7 @@ async def list_devices(
|
|||||||
devices = query.offset(skip).limit(limit).all()
|
devices = query.offset(skip).limit(limit).all()
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
@router.get("/{device_id}", response_model=DeviceSchema)
|
@router.get("/{device_id}", response_model=DeviceSchema, dependencies=[Depends(get_api_key)])
|
||||||
async def get_device(device_id: str, db: Session = Depends(get_db)):
|
async def get_device(device_id: str, db: Session = Depends(get_db)):
|
||||||
"""
|
"""
|
||||||
获取设备详情
|
获取设备详情
|
||||||
@@ -81,7 +81,7 @@ async def get_device(device_id: str, db: Session = Depends(get_db)):
|
|||||||
)
|
)
|
||||||
return device
|
return device
|
||||||
|
|
||||||
@router.put("/{device_id}", response_model=DeviceSchema)
|
@router.put("/{device_id}", response_model=DeviceSchema, dependencies=[Depends(get_api_key)])
|
||||||
async def update_device(
|
async def update_device(
|
||||||
device_id: str,
|
device_id: str,
|
||||||
device_update: DeviceUpdate,
|
device_update: DeviceUpdate,
|
||||||
@@ -108,7 +108,7 @@ async def update_device(
|
|||||||
|
|
||||||
return device
|
return device
|
||||||
|
|
||||||
@router.delete("/{device_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{device_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(get_api_key)])
|
||||||
async def delete_device(device_id: str, db: Session = Depends(get_db)):
|
async def delete_device(device_id: str, db: Session = Depends(get_db)):
|
||||||
"""
|
"""
|
||||||
删除设备
|
删除设备
|
||||||
@@ -126,11 +126,12 @@ async def delete_device(device_id: str, db: Session = Depends(get_db)):
|
|||||||
db.delete(device)
|
db.delete(device)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
@router.get("/{device_id}/bootstrap", response_model=BootstrapResponse)
|
@router.post("/{device_id}/bootstrap", response_model=BootstrapResponse, dependencies=[Depends(get_api_key)])
|
||||||
async def device_bootstrap(device_id: str, db: Session = Depends(get_db)):
|
async def bootstrap_device(device_id: str, db: Session = Depends(get_db)):
|
||||||
"""
|
"""
|
||||||
设备启动获取当前版本信息
|
设备引导 - 获取设备配置和内容
|
||||||
"""
|
"""
|
||||||
|
# 验证设备是否存在
|
||||||
device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first()
|
device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first()
|
||||||
if not device:
|
if not device:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@@ -138,29 +139,19 @@ async def device_bootstrap(device_id: str, db: Session = Depends(get_db)):
|
|||||||
detail="设备不存在"
|
detail="设备不存在"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 更新设备最后在线时间
|
# 获取设备场景的内容
|
||||||
device.last_online = datetime.utcnow()
|
contents = db.query(ContentModel).filter(ContentModel.scene == device.scene).all()
|
||||||
db.commit()
|
|
||||||
|
|
||||||
# 获取最新的活跃内容
|
|
||||||
latest_content = db.query(ContentModel).filter(
|
|
||||||
ContentModel.device_id == device_id,
|
|
||||||
ContentModel.is_active == True
|
|
||||||
).order_by(ContentModel.version.desc()).first()
|
|
||||||
|
|
||||||
|
# 构建响应
|
||||||
response = BootstrapResponse(
|
response = BootstrapResponse(
|
||||||
device_id=device_id,
|
device_id=device_id,
|
||||||
timezone=latest_content.timezone if latest_content else "Asia/Shanghai",
|
scene=device.scene,
|
||||||
time_format=latest_content.time_format if latest_content else "%Y-%m-%d %H:%M"
|
contents=contents
|
||||||
)
|
)
|
||||||
|
|
||||||
if latest_content:
|
|
||||||
response.content_version = latest_content.version
|
|
||||||
response.last_updated = latest_content.created_at
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@router.get("/{device_id}/status")
|
@router.get("/{device_id}/status", dependencies=[Depends(get_api_key)])
|
||||||
async def get_device_status(device_id: str, db: Session = Depends(get_db)):
|
async def get_device_status(device_id: str, db: Session = Depends(get_db)):
|
||||||
"""
|
"""
|
||||||
获取设备状态
|
获取设备状态
|
||||||
|
|||||||
168
api/todos.py
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, status, Security
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from typing import List, Optional
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from database import get_db
|
||||||
|
from schemas import Todo as TodoSchema, TodoCreate, TodoUpdate
|
||||||
|
from models import Todo as TodoModel
|
||||||
|
from database import Device as DeviceModel
|
||||||
|
from auth import get_api_key
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
tags=["todos"]
|
||||||
|
)
|
||||||
|
|
||||||
|
@router.post("/", response_model=TodoSchema, status_code=status.HTTP_201_CREATED, dependencies=[Depends(get_api_key)])
|
||||||
|
async def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
创建新的待办事项
|
||||||
|
"""
|
||||||
|
# 检查设备是否存在
|
||||||
|
device = db.query(DeviceModel).filter(DeviceModel.device_id == todo.device_id).first()
|
||||||
|
if not device:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="设备不存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建新的待办事项
|
||||||
|
db_todo = TodoModel(
|
||||||
|
title=todo.title,
|
||||||
|
description=todo.description,
|
||||||
|
device_id=todo.device_id,
|
||||||
|
due_date=todo.due_date
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(db_todo)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_todo)
|
||||||
|
|
||||||
|
return db_todo
|
||||||
|
|
||||||
|
@router.get("/", response_model=List[TodoSchema], dependencies=[Depends(get_api_key)])
|
||||||
|
async def list_todos(
|
||||||
|
skip: int = 0,
|
||||||
|
limit: int = 100,
|
||||||
|
device_id: Optional[str] = None,
|
||||||
|
is_completed: Optional[bool] = None,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
获取待办事项列表
|
||||||
|
"""
|
||||||
|
query = db.query(TodoModel)
|
||||||
|
|
||||||
|
if device_id:
|
||||||
|
query = query.filter(TodoModel.device_id == device_id)
|
||||||
|
|
||||||
|
if is_completed is not None:
|
||||||
|
query = query.filter(TodoModel.is_completed == is_completed)
|
||||||
|
|
||||||
|
todos = query.order_by(TodoModel.created_at.desc()).offset(skip).limit(limit).all()
|
||||||
|
return todos
|
||||||
|
|
||||||
|
@router.get("/{todo_id}", response_model=TodoSchema, dependencies=[Depends(get_api_key)])
|
||||||
|
async def get_todo(todo_id: int, db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
获取待办事项详情
|
||||||
|
"""
|
||||||
|
todo = db.query(TodoModel).filter(TodoModel.id == todo_id).first()
|
||||||
|
if not todo:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="待办事项不存在"
|
||||||
|
)
|
||||||
|
return todo
|
||||||
|
|
||||||
|
@router.put("/{todo_id}", response_model=TodoSchema, dependencies=[Depends(get_api_key)])
|
||||||
|
async def update_todo(
|
||||||
|
todo_id: int,
|
||||||
|
todo_update: TodoUpdate,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
更新待办事项
|
||||||
|
"""
|
||||||
|
todo = db.query(TodoModel).filter(TodoModel.id == todo_id).first()
|
||||||
|
if not todo:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="待办事项不存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 更新待办事项信息
|
||||||
|
update_data = todo_update.model_dump(exclude_unset=True)
|
||||||
|
|
||||||
|
# 如果状态从未完成变为完成,设置完成时间
|
||||||
|
if "is_completed" in update_data and update_data["is_completed"] and not todo.is_completed:
|
||||||
|
update_data["completed_at"] = datetime.utcnow()
|
||||||
|
# 如果状态从完成变为未完成,清除完成时间
|
||||||
|
elif "is_completed" in update_data and not update_data["is_completed"] and todo.is_completed:
|
||||||
|
update_data["completed_at"] = None
|
||||||
|
|
||||||
|
for field, value in update_data.items():
|
||||||
|
setattr(todo, field, value)
|
||||||
|
|
||||||
|
todo.updated_at = datetime.utcnow()
|
||||||
|
db.commit()
|
||||||
|
db.refresh(todo)
|
||||||
|
|
||||||
|
return todo
|
||||||
|
|
||||||
|
@router.delete("/{todo_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(get_api_key)])
|
||||||
|
async def delete_todo(todo_id: int, db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
删除待办事项
|
||||||
|
"""
|
||||||
|
todo = db.query(TodoModel).filter(TodoModel.id == todo_id).first()
|
||||||
|
if not todo:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="待办事项不存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
db.delete(todo)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
@router.post("/{todo_id}/complete", response_model=TodoSchema, dependencies=[Depends(get_api_key)])
|
||||||
|
async def complete_todo(todo_id: int, db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
标记待办事项为完成
|
||||||
|
"""
|
||||||
|
todo = db.query(TodoModel).filter(TodoModel.id == todo_id).first()
|
||||||
|
if not todo:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="待办事项不存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
todo.is_completed = True
|
||||||
|
todo.completed_at = datetime.utcnow()
|
||||||
|
todo.updated_at = datetime.utcnow()
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
db.refresh(todo)
|
||||||
|
|
||||||
|
return todo
|
||||||
|
|
||||||
|
@router.post("/{todo_id}/incomplete", response_model=TodoSchema, dependencies=[Depends(get_api_key)])
|
||||||
|
async def incomplete_todo(todo_id: int, db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
标记待办事项为未完成
|
||||||
|
"""
|
||||||
|
todo = db.query(TodoModel).filter(TodoModel.id == todo_id).first()
|
||||||
|
if not todo:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="待办事项不存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
todo.is_completed = False
|
||||||
|
todo.completed_at = None
|
||||||
|
todo.updated_at = datetime.utcnow()
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
db.refresh(todo)
|
||||||
|
|
||||||
|
return todo
|
||||||
128
auth.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
from fastapi import HTTPException, status, Request, Security, Depends
|
||||||
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials, APIKeyHeader
|
||||||
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
|
from starlette.responses import Response
|
||||||
|
from config import settings
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 创建API Key安全方案
|
||||||
|
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
|
||||||
|
|
||||||
|
async def get_api_key(api_key: str = Security(api_key_header)):
|
||||||
|
"""
|
||||||
|
API Key依赖项,用于路由级别的鉴权
|
||||||
|
"""
|
||||||
|
if not api_key:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="缺少API Key",
|
||||||
|
headers={"WWW-Authenticate": "ApiKey"},
|
||||||
|
)
|
||||||
|
|
||||||
|
if api_key != settings.secret_key:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="无效的API Key",
|
||||||
|
headers={"WWW-Authenticate": "ApiKey"},
|
||||||
|
)
|
||||||
|
|
||||||
|
return api_key
|
||||||
|
|
||||||
|
class APIKeyMiddleware(BaseHTTPMiddleware):
|
||||||
|
"""
|
||||||
|
API Key鉴权中间件
|
||||||
|
验证请求中的Secret Key
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def dispatch(self, request: Request, call_next):
|
||||||
|
# 跳过不需要鉴权的路径
|
||||||
|
if self._should_skip_auth(request.url.path):
|
||||||
|
return await call_next(request)
|
||||||
|
|
||||||
|
# 检查API Key
|
||||||
|
api_key = request.headers.get("X-API-Key")
|
||||||
|
if not api_key:
|
||||||
|
logger.warning(f"缺少API Key: {request.method} {request.url.path}")
|
||||||
|
return Response(
|
||||||
|
content='{"detail": "缺少API Key"}',
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
media_type="application/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 验证API Key
|
||||||
|
if api_key != settings.secret_key:
|
||||||
|
logger.warning(f"无效的API Key: {request.method} {request.url.path}")
|
||||||
|
return Response(
|
||||||
|
content='{"detail": "无效的API Key"}',
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
media_type="application/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
return await call_next(request)
|
||||||
|
|
||||||
|
def _should_skip_auth(self, path: str) -> bool:
|
||||||
|
"""
|
||||||
|
判断是否跳过鉴权的路径
|
||||||
|
"""
|
||||||
|
# 公共API路径不需要鉴权
|
||||||
|
public_api_paths = [
|
||||||
|
"/api/contents/public/",
|
||||||
|
]
|
||||||
|
|
||||||
|
# 检查是否是公共API路径
|
||||||
|
for public_path in public_api_paths:
|
||||||
|
if path.startswith(public_path):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 所有其他API路径都需要鉴权,不跳过
|
||||||
|
# 如果路径以/api开头,则不跳过(需要鉴权)
|
||||||
|
if path.startswith("/api"):
|
||||||
|
return False
|
||||||
|
|
||||||
|
skip_paths = [
|
||||||
|
"/",
|
||||||
|
"/health",
|
||||||
|
"/docs",
|
||||||
|
"/redoc",
|
||||||
|
"/openapi.json",
|
||||||
|
"/admin",
|
||||||
|
"/admin/login",
|
||||||
|
"/static",
|
||||||
|
]
|
||||||
|
|
||||||
|
# 检查是否以跳过路径开头
|
||||||
|
for skip_path in skip_paths:
|
||||||
|
if path.startswith(skip_path):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class AdminAuthMiddleware(BaseHTTPMiddleware):
|
||||||
|
"""
|
||||||
|
Admin页面认证中间件
|
||||||
|
验证用户是否已登录
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def dispatch(self, request: Request, call_next):
|
||||||
|
# 只对admin路径进行认证
|
||||||
|
if not request.url.path.startswith("/admin") or request.url.path == "/admin/login":
|
||||||
|
return await call_next(request)
|
||||||
|
|
||||||
|
# 检查会话
|
||||||
|
if not self._is_authenticated(request):
|
||||||
|
# 重定向到登录页面
|
||||||
|
from fastapi.responses import RedirectResponse
|
||||||
|
return RedirectResponse(url="/admin/login?next=" + request.url.path, status_code=303)
|
||||||
|
|
||||||
|
return await call_next(request)
|
||||||
|
|
||||||
|
def _is_authenticated(self, request: Request) -> bool:
|
||||||
|
"""
|
||||||
|
检查用户是否已认证
|
||||||
|
"""
|
||||||
|
# 从session中获取认证信息
|
||||||
|
session = request.session
|
||||||
|
return session.get("authenticated", False)
|
||||||
13
config.py
@@ -2,8 +2,10 @@ from pydantic_settings import BaseSettings
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
# 数据库配置
|
# 数据库配置1
|
||||||
database_url: str = "postgresql://luna:123luna@121.43.104.161:6432/luna_ink"
|
#database_url: str = "postgresql://luna:123luna@121.43.104.161:6432/luna"
|
||||||
|
database_url: str = "postgresql://luna:123luna@6.6.6.66:5432/luna"
|
||||||
|
|
||||||
|
|
||||||
# MQTT配置
|
# MQTT配置
|
||||||
mqtt_broker_host: str = "localhost"
|
mqtt_broker_host: str = "localhost"
|
||||||
@@ -25,11 +27,16 @@ class Settings(BaseSettings):
|
|||||||
ink_height: int = 300
|
ink_height: int = 300
|
||||||
|
|
||||||
# 安全配置
|
# 安全配置
|
||||||
secret_key: str = "your-secret-key-change-in-production"
|
secret_key: str = "123tangledup-ai"
|
||||||
algorithm: str = "HS256"
|
algorithm: str = "HS256"
|
||||||
access_token_expire_minutes: int = 30
|
access_token_expire_minutes: int = 30
|
||||||
|
|
||||||
|
# 管理员配置
|
||||||
|
admin_username: str = "admin"
|
||||||
|
admin_password: str = "123456"
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
env_file = ".env"
|
env_file = ".env"
|
||||||
|
case_sensitive = False
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
16
database.py
@@ -40,6 +40,22 @@ class Content(Base):
|
|||||||
# 关联设备
|
# 关联设备
|
||||||
device = relationship("Device", back_populates="contents")
|
device = relationship("Device", back_populates="contents")
|
||||||
|
|
||||||
|
class Todo(Base):
|
||||||
|
__tablename__ = "todos"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
title = Column(String(200), nullable=False)
|
||||||
|
description = Column(Text, nullable=True)
|
||||||
|
device_id = Column(String(50), ForeignKey("devices.device_id"), nullable=False)
|
||||||
|
is_completed = Column(Boolean, default=False)
|
||||||
|
due_date = Column(DateTime, nullable=True)
|
||||||
|
completed_at = Column(DateTime, nullable=True)
|
||||||
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
# 关联设备
|
||||||
|
device = relationship("Device")
|
||||||
|
|
||||||
# 创建数据库连接
|
# 创建数据库连接
|
||||||
engine = create_engine(settings.database_url)
|
engine = create_engine(settings.database_url)
|
||||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|||||||
45
docker-compose.yml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
services:
|
||||||
|
# 主应用服务
|
||||||
|
luna-app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: luna-app
|
||||||
|
ports:
|
||||||
|
- "8199:8199"
|
||||||
|
volumes:
|
||||||
|
- ./static:/app/static
|
||||||
|
env_file:
|
||||||
|
- .env.docker
|
||||||
|
depends_on:
|
||||||
|
- luna-mqtt
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- luna-network
|
||||||
|
|
||||||
|
# MQTT服务 (使用Eclipse Mosquitto)
|
||||||
|
luna-mqtt:
|
||||||
|
image: eclipse-mosquitto:2.0
|
||||||
|
container_name: luna-mqtt
|
||||||
|
ports:
|
||||||
|
- "1883:1883"
|
||||||
|
- "9001:9001"
|
||||||
|
volumes:
|
||||||
|
- ./mosquitto.conf:/mosquitto/config/mosquitto.conf
|
||||||
|
- mosquitto_data:/mosquitto/data
|
||||||
|
- mosquitto_logs:/mosquitto/log
|
||||||
|
- mosquitto_config:/mosquitto/config
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- luna-network
|
||||||
|
|
||||||
|
# 数据卷
|
||||||
|
volumes:
|
||||||
|
mosquitto_data:
|
||||||
|
mosquitto_logs:
|
||||||
|
mosquitto_config:
|
||||||
|
|
||||||
|
# 网络
|
||||||
|
networks:
|
||||||
|
luna-network:
|
||||||
|
driver: bridge
|
||||||
292
docker_deplay.sh
Executable file
@@ -0,0 +1,292 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Docker 镜像构建和部署自动化脚本
|
||||||
|
# chmod u+x docker_deplay.sh
|
||||||
|
# 用法:
|
||||||
|
# ./docker_deplay.sh # 完整构建和部署流程 (默认AMD64架构)
|
||||||
|
# ./docker_deplay.sh -amd # 构建和部署AMD64架构
|
||||||
|
# ./docker_deplay.sh -arm # 构建和部署ARM64架构
|
||||||
|
# ./docker_deplay.sh -upload # 仅上传已存在的tar文件并部署
|
||||||
|
# ./docker_deplay.sh -upload -amd # 仅上传已存在的AMD64架构tar文件并部署
|
||||||
|
# ./docker_deplay.sh -upload -arm # 仅上传已存在的ARM64架构tar文件并部署
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# 配置变量 - 请根据实际情况修改
|
||||||
|
SERVER_HOST="6.6.6.66" # 服务器IP地址
|
||||||
|
SERVER_USER="ubuntu" # 服务器用户名
|
||||||
|
SERVER_PASSWORD="qweasdzxc1" # 服务器密码
|
||||||
|
SERVER_PORT="22" # SSH端口,默认22
|
||||||
|
IMAGE_NAME="epage_server" # Docker镜像名称
|
||||||
|
IMAGE_TAG="latest" # Docker镜像标签
|
||||||
|
CONTAINER_NAME="epage_server-container" # 容器名称
|
||||||
|
LOCAL_PORT="8199" # 本地端口
|
||||||
|
CONTAINER_PORT="8199" # 容器端口
|
||||||
|
TAR_FILE="${IMAGE_NAME}-${IMAGE_TAG}.tar" # 压缩包文件名
|
||||||
|
|
||||||
|
# 架构相关变量
|
||||||
|
PLATFORM="linux/amd64" # 默认架构
|
||||||
|
ARCH_SUFFIX="" # 架构后缀,用于区分不同架构的tar文件
|
||||||
|
|
||||||
|
# 颜色输出
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# 日志函数
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查依赖
|
||||||
|
check_dependencies() {
|
||||||
|
log_info "检查依赖..."
|
||||||
|
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
log_error "Docker 未安装,请先安装 Docker"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v sshpass &> /dev/null; then
|
||||||
|
log_error "sshpass 未安装,请先安装 sshpass"
|
||||||
|
log_info "macOS: brew install sshpass"
|
||||||
|
log_info "Ubuntu: sudo apt-get install sshpass"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "依赖检查完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 解析命令行参数
|
||||||
|
parse_arguments() {
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-amd)
|
||||||
|
PLATFORM="linux/amd64"
|
||||||
|
ARCH_SUFFIX="-amd64"
|
||||||
|
log_info "设置目标架构为 AMD64"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-arm)
|
||||||
|
PLATFORM="linux/arm64"
|
||||||
|
ARCH_SUFFIX="-arm64"
|
||||||
|
log_info "设置目标架构为 ARM64"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-upload)
|
||||||
|
UPLOAD_ONLY=true
|
||||||
|
log_info "设置为仅上传模式"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "未知参数: $1"
|
||||||
|
log_info "支持的参数: -amd, -arm, -upload"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# 更新TAR_FILE名,包含架构后缀
|
||||||
|
TAR_FILE="${IMAGE_NAME}-${IMAGE_TAG}${ARCH_SUFFIX}.tar"
|
||||||
|
log_info "镜像文件名: ${TAR_FILE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 构建Docker镜像
|
||||||
|
build_image() {
|
||||||
|
log_info "开始构建 Docker 镜像..."
|
||||||
|
|
||||||
|
# 检查是否存在旧的tar文件
|
||||||
|
if [ -f "$TAR_FILE" ]; then
|
||||||
|
log_warning "发现旧的tar文件,正在删除..."
|
||||||
|
rm -f "$TAR_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 构建镜像并导出为tar文件
|
||||||
|
docker buildx build --platform $PLATFORM -t "${IMAGE_NAME}:${IMAGE_TAG}" --output type=docker,dest="./${TAR_FILE}" .
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
log_success "Docker 镜像构建完成: ${TAR_FILE}"
|
||||||
|
else
|
||||||
|
log_error "Docker 镜像构建失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 上传文件到服务器
|
||||||
|
upload_to_server() {
|
||||||
|
log_info "上传文件到服务器..."
|
||||||
|
|
||||||
|
sshpass -p "$SERVER_PASSWORD" scp -P "$SERVER_PORT" -o StrictHostKeyChecking=no "$TAR_FILE" "${SERVER_USER}@${SERVER_HOST}:/tmp/"
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
log_success "文件上传成功"
|
||||||
|
else
|
||||||
|
log_error "文件上传失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 在服务器上部署
|
||||||
|
deploy_on_server() {
|
||||||
|
log_info "在服务器上部署..."
|
||||||
|
|
||||||
|
sshpass -p "$SERVER_PASSWORD" ssh -p "$SERVER_PORT" -o StrictHostKeyChecking=no "${SERVER_USER}@${SERVER_HOST}" << EOF
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "[INFO] 开始服务器端部署..."
|
||||||
|
|
||||||
|
# 检查并停止现有容器
|
||||||
|
if sudo docker ps -a --format 'table {{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||||
|
echo "[INFO] 发现现有容器 ${CONTAINER_NAME},正在停止并删除..."
|
||||||
|
sudo docker stop ${CONTAINER_NAME} || true
|
||||||
|
sudo docker rm ${CONTAINER_NAME} || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查并删除现有镜像
|
||||||
|
if sudo docker images --format 'table {{.Repository}}:{{.Tag}}' | grep -q "^${IMAGE_NAME}:${IMAGE_TAG}$"; then
|
||||||
|
echo "[INFO] 发现现有镜像 ${IMAGE_NAME}:${IMAGE_TAG},正在删除..."
|
||||||
|
sudo docker rmi ${IMAGE_NAME}:${IMAGE_TAG} || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 加载新镜像
|
||||||
|
echo "[INFO] 加载新镜像..."
|
||||||
|
sudo docker load -i /tmp/${TAR_FILE}
|
||||||
|
|
||||||
|
# 验证镜像是否加载成功
|
||||||
|
if sudo docker images | grep -q "${IMAGE_NAME}"; then
|
||||||
|
echo "[SUCCESS] 镜像加载成功"
|
||||||
|
else
|
||||||
|
echo "[ERROR] 镜像加载失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 运行新容器
|
||||||
|
echo "[INFO] 启动新容器..."
|
||||||
|
sudo docker run -d -p ${LOCAL_PORT}:${CONTAINER_PORT} --name ${CONTAINER_NAME} ${IMAGE_NAME}:${IMAGE_TAG}
|
||||||
|
|
||||||
|
# 验证容器是否启动成功
|
||||||
|
if sudo docker ps | grep -q "${CONTAINER_NAME}"; then
|
||||||
|
echo "[SUCCESS] 容器启动成功"
|
||||||
|
echo "[INFO] 容器状态:"
|
||||||
|
sudo docker ps | grep "${CONTAINER_NAME}"
|
||||||
|
else
|
||||||
|
echo "[ERROR] 容器启动失败"
|
||||||
|
echo "[INFO] 查看容器日志:"
|
||||||
|
sudo docker logs ${CONTAINER_NAME}
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 清理临时文件
|
||||||
|
echo "[INFO] 清理临时文件..."
|
||||||
|
rm -f /tmp/${TAR_FILE}
|
||||||
|
|
||||||
|
echo "[SUCCESS] 部署完成!"
|
||||||
|
echo "[INFO] 应用访问地址: http://${SERVER_HOST}:${LOCAL_PORT}"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
log_success "服务器部署完成"
|
||||||
|
else
|
||||||
|
log_error "服务器部署失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 清理本地文件
|
||||||
|
cleanup_local() {
|
||||||
|
log_info "清理本地临时文件..."
|
||||||
|
|
||||||
|
if [ -f "$TAR_FILE" ]; then
|
||||||
|
rm -f "$TAR_FILE"
|
||||||
|
log_success "本地临时文件已清理"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 显示部署信息
|
||||||
|
show_deployment_info() {
|
||||||
|
log_success "部署完成!"
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "部署信息:"
|
||||||
|
echo "=========================================="
|
||||||
|
echo "服务器地址: ${SERVER_HOST}"
|
||||||
|
echo "应用端口: ${LOCAL_PORT}"
|
||||||
|
echo "访问地址: http://${SERVER_HOST}:${LOCAL_PORT}"
|
||||||
|
echo "容器名称: ${CONTAINER_NAME}"
|
||||||
|
echo "镜像名称: ${IMAGE_NAME}:${IMAGE_TAG}"
|
||||||
|
echo "目标架构: ${PLATFORM}"
|
||||||
|
echo "镜像文件: ${TAR_FILE}"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
log_info "如需查看容器日志,请在服务器上运行: sudo docker logs ${CONTAINER_NAME}"
|
||||||
|
log_info "如需停止容器,请在服务器上运行: sudo docker stop ${CONTAINER_NAME}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 主函数
|
||||||
|
main() {
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Docker 镜像构建和部署自动化脚本"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 解析命令行参数
|
||||||
|
UPLOAD_ONLY=false
|
||||||
|
parse_arguments "$@"
|
||||||
|
|
||||||
|
# 检查配置
|
||||||
|
if [ "$SERVER_HOST" = "your-server-ip" ] || [ "$SERVER_PASSWORD" = "your-password" ]; then
|
||||||
|
log_error "请先配置脚本顶部的服务器信息"
|
||||||
|
log_info "需要修改的变量:"
|
||||||
|
log_info " - SERVER_HOST: 服务器IP地址"
|
||||||
|
log_info " - SERVER_USER: 服务器用户名"
|
||||||
|
log_info " - SERVER_PASSWORD: 服务器密码"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查是否是上传模式
|
||||||
|
if [ "$UPLOAD_ONLY" = true ]; then
|
||||||
|
log_info "检测到 -upload 参数,跳过构建步骤"
|
||||||
|
|
||||||
|
# 检查tar文件是否存在
|
||||||
|
if [ ! -f "$TAR_FILE" ]; then
|
||||||
|
log_error "未找到tar文件: $TAR_FILE"
|
||||||
|
log_info "请先运行脚本构建镜像,或确保tar文件存在"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "找到tar文件: $TAR_FILE"
|
||||||
|
|
||||||
|
# 执行上传和部署流程
|
||||||
|
upload_to_server
|
||||||
|
deploy_on_server
|
||||||
|
cleanup_local
|
||||||
|
show_deployment_info
|
||||||
|
else
|
||||||
|
# 执行完整的部署流程
|
||||||
|
check_dependencies
|
||||||
|
build_image
|
||||||
|
upload_to_server
|
||||||
|
deploy_on_server
|
||||||
|
cleanup_local
|
||||||
|
show_deployment_info
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 脚本入口
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
main "$@"
|
||||||
|
fi
|
||||||
0
luna_20260302_1211.dump
Normal file
30
main.py
@@ -1,6 +1,7 @@
|
|||||||
from fastapi import FastAPI, Request
|
from fastapi import FastAPI, Request
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from starlette.middleware.sessions import SessionMiddleware
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@@ -10,6 +11,7 @@ from database import init_db
|
|||||||
from mqtt_manager import mqtt_manager
|
from mqtt_manager import mqtt_manager
|
||||||
from api import api_router
|
from api import api_router
|
||||||
from admin_routes import admin_router
|
from admin_routes import admin_router
|
||||||
|
from auth import APIKeyMiddleware, AdminAuthMiddleware
|
||||||
|
|
||||||
# 配置日志
|
# 配置日志
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
@@ -51,10 +53,21 @@ async def lifespan(app: FastAPI):
|
|||||||
|
|
||||||
# 创建FastAPI应用
|
# 创建FastAPI应用
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title=settings.app_name,
|
title="墨水屏桌面屏幕系统 API",
|
||||||
description="基于 FastAPI + MQTT + HTTP/HTTPS + NTP 的轻量级墨水屏显示系统服务端",
|
description="用于管理墨水屏设备、内容和待办事项的API",
|
||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
lifespan=lifespan
|
lifespan=lifespan,
|
||||||
|
openapi_components={
|
||||||
|
"securitySchemes": {
|
||||||
|
"APIKeyHeader": {
|
||||||
|
"type": "apiKey",
|
||||||
|
"in": "header",
|
||||||
|
"name": "X-API-Key",
|
||||||
|
"description": "API Key鉴权,请在下方输入正确的API Key"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
security=[{"APIKeyHeader": []}]
|
||||||
)
|
)
|
||||||
|
|
||||||
# 添加CORS中间件
|
# 添加CORS中间件
|
||||||
@@ -66,11 +79,20 @@ app.add_middleware(
|
|||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 添加API Key鉴权中间件
|
||||||
|
app.add_middleware(APIKeyMiddleware)
|
||||||
|
|
||||||
|
# 添加Admin认证中间件
|
||||||
|
app.add_middleware(AdminAuthMiddleware)
|
||||||
|
|
||||||
|
# 添加Session中间件
|
||||||
|
app.add_middleware(SessionMiddleware, secret_key=settings.secret_key)
|
||||||
|
|
||||||
# 挂载静态文件
|
# 挂载静态文件
|
||||||
app.mount("/static", StaticFiles(directory=settings.static_dir), name="static")
|
app.mount("/static", StaticFiles(directory=settings.static_dir), name="static")
|
||||||
|
|
||||||
# 注册API路由
|
# 注册API路由
|
||||||
app.include_router(api_router)
|
app.include_router(api_router, prefix="/api")
|
||||||
|
|
||||||
# 包含管理后台路由
|
# 包含管理后台路由
|
||||||
app.include_router(admin_router, prefix="/admin", tags=["管理后台"])
|
app.include_router(admin_router, prefix="/admin", tags=["管理后台"])
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ from sqlalchemy.orm import Session
|
|||||||
from database import Base
|
from database import Base
|
||||||
|
|
||||||
# 导入所有模型以确保它们被注册到Base.metadata
|
# 导入所有模型以确保它们被注册到Base.metadata
|
||||||
from database import Device, Content
|
from database import Device, Content, Todo
|
||||||
|
|
||||||
__all__ = ["Device", "Content"]
|
__all__ = ["Device", "Content", "Todo"]
|
||||||
10
mosquitto.conf
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Mosquitto配置文件
|
||||||
|
|
||||||
|
# 监听端口
|
||||||
|
listener 1883
|
||||||
|
|
||||||
|
# 允许匿名连接(默认为true,我们将设置为false以要求认证)
|
||||||
|
allow_anonymous false
|
||||||
|
|
||||||
|
# 密码文件
|
||||||
|
password_file /mosquitto/config/passwd
|
||||||
@@ -165,6 +165,43 @@ class MQTTManager:
|
|||||||
logger.info(f"已取消订阅设备 {device_id} 状态")
|
logger.info(f"已取消订阅设备 {device_id} 状态")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"取消订阅设备状态失败: {str(e)}")
|
logger.error(f"取消订阅设备状态失败: {str(e)}")
|
||||||
|
|
||||||
|
def send_todo_command(self, device_id: str, action: str, todo_data: Dict[str, Any]) -> bool:
|
||||||
|
"""
|
||||||
|
发送待办事项命令
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device_id: 设备ID
|
||||||
|
action: 动作类型 (create, update, delete)
|
||||||
|
todo_data: 待办事项数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否发送成功
|
||||||
|
"""
|
||||||
|
if not self.connected:
|
||||||
|
logger.error("MQTT未连接,无法发送待办事项命令")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
topic = f"esp32/{device_id}/todo"
|
||||||
|
payload = {
|
||||||
|
"type": "todo",
|
||||||
|
"action": action,
|
||||||
|
"data": todo_data,
|
||||||
|
"timestamp": int(time.time())
|
||||||
|
}
|
||||||
|
|
||||||
|
result = self.client.publish(topic, json.dumps(payload))
|
||||||
|
if result.rc == mqtt.MQTT_ERR_SUCCESS:
|
||||||
|
logger.info(f"成功向设备 {device_id} 发送待办事项命令: {action}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.error(f"向设备 {device_id} 发送待办事项命令失败,错误码: {result.rc}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"发送待办事项命令失败: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
# 全局MQTT管理器实例
|
# 全局MQTT管理器实例
|
||||||
mqtt_manager = MQTTManager()
|
mqtt_manager = MQTTManager()
|
||||||
@@ -10,4 +10,6 @@ pydantic-settings==2.1.0
|
|||||||
python-jose[cryptography]==3.3.0
|
python-jose[cryptography]==3.3.0
|
||||||
passlib[bcrypt]==1.7.4
|
passlib[bcrypt]==1.7.4
|
||||||
aiofiles==23.2.1
|
aiofiles==23.2.1
|
||||||
httpx==0.25.2
|
httpx==0.25.2
|
||||||
|
itsdangerous==2.1.2
|
||||||
|
jinja2==3.1.2
|
||||||
28
schemas.py
@@ -86,4 +86,30 @@ class MQTTStatus(BaseModel):
|
|||||||
content_version: Optional[int] = None
|
content_version: Optional[int] = None
|
||||||
timestamp: int = Field(..., description="时间戳")
|
timestamp: int = Field(..., description="时间戳")
|
||||||
device_id: str = Field(..., description="设备ID")
|
device_id: str = Field(..., description="设备ID")
|
||||||
message: Optional[str] = None
|
message: Optional[str] = None
|
||||||
|
|
||||||
|
# 待办事项相关模型
|
||||||
|
class TodoBase(BaseModel):
|
||||||
|
title: str = Field(..., description="待办事项标题")
|
||||||
|
description: Optional[str] = Field(None, description="待办事项描述")
|
||||||
|
due_date: Optional[datetime] = Field(None, description="截止日期")
|
||||||
|
|
||||||
|
class TodoCreate(TodoBase):
|
||||||
|
device_id: str = Field(..., description="设备ID")
|
||||||
|
|
||||||
|
class TodoUpdate(BaseModel):
|
||||||
|
title: Optional[str] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
due_date: Optional[datetime] = None
|
||||||
|
is_completed: Optional[bool] = None
|
||||||
|
|
||||||
|
class Todo(TodoBase):
|
||||||
|
id: int
|
||||||
|
device_id: str
|
||||||
|
is_completed: bool
|
||||||
|
completed_at: Optional[datetime] = None
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: datetime
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
11
setup_mqtt_auth.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 创建MQTT密码文件
|
||||||
|
docker exec luna-mqtt mosquitto_passwd -c -b /mosquitto/config/passwd luna2025 123luna2021
|
||||||
|
|
||||||
|
# 重启MQTT服务以应用新的认证配置
|
||||||
|
docker restart luna-mqtt
|
||||||
|
|
||||||
|
echo "MQTT用户名和密码已配置完成"
|
||||||
|
echo "用户名: luna2025"
|
||||||
|
echo "密码: 123luna2021"
|
||||||
BIN
static/.DS_Store
vendored
Normal file
BIN
static/processed/2f4215c6-62cf-4c5d-b1c7-c4e0b7b26b94.bmp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
static/processed/af90fec7-7b6a-4ce6-bbac-cee44fdd6c22.bmp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
static/processed/d9af5c53-ddb3-4093-ac62-607ef1a332f6.bmp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
static/uploads/7e9f852a-683a-4e81-8281-00b8d857cb4e.png
Normal file
|
After Width: | Height: | Size: 457 KiB |
BIN
static/uploads/8051db84-3062-476b-9fe9-c59437561a95.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
static/uploads/957ebda9-1c3d-4b93-bbbc-fc5351479034.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
@@ -35,6 +35,11 @@
|
|||||||
<i class="fas fa-file-image me-1"></i>内容管理
|
<i class="fas fa-file-image me-1"></i>内容管理
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if '/admin/todos' in request.url.path %}active{% endif %}" href="/admin/todos">
|
||||||
|
<i class="fas fa-tasks me-1"></i>待办事项
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {% if '/admin/upload' in request.url.path %}active{% endif %}" href="/admin/upload">
|
<a class="nav-link {% if '/admin/upload' in request.url.path %}active{% endif %}" href="/admin/upload">
|
||||||
<i class="fas fa-upload me-1"></i>图片上传
|
<i class="fas fa-upload me-1"></i>图片上传
|
||||||
@@ -45,7 +50,22 @@
|
|||||||
<button class="btn btn-outline-light me-2" id="themeToggle" title="切换主题">
|
<button class="btn btn-outline-light me-2" id="themeToggle" title="切换主题">
|
||||||
<i class="fas fa-palette"></i>
|
<i class="fas fa-palette"></i>
|
||||||
</button>
|
</button>
|
||||||
<span class="navbar-text text-light">
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-outline-light dropdown-toggle" type="button" id="userDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="fas fa-user-circle me-1"></i>
|
||||||
|
{% if request.session.get('username') %}
|
||||||
|
{{ request.session.get('username') }}
|
||||||
|
{% else %}
|
||||||
|
管理员
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
|
||||||
|
<li><a class="dropdown-item" href="/admin/logout">
|
||||||
|
<i class="fas fa-sign-out-alt me-1"></i>登出
|
||||||
|
</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<span class="navbar-text text-light ms-2">
|
||||||
<i class="far fa-clock me-1"></i>
|
<i class="far fa-clock me-1"></i>
|
||||||
<span id="currentTime"></span>
|
<span id="currentTime"></span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -100,20 +100,45 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-5">
|
<div class="col-5">
|
||||||
<div class="icon-big text-center">
|
<div class="icon-big text-center">
|
||||||
<i class="fas fa-play-circle"></i>
|
<i class="fas fa-tasks"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-7">
|
<div class="col-7">
|
||||||
<div class="numbers">
|
<div class="numbers">
|
||||||
<p class="card-category">活跃内容</p>
|
<p class="card-category">待办事项</p>
|
||||||
<h3 class="card-title">{{ active_content_count }}</h3>
|
<h3 class="card-title">{{ todo_count }}</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<i class="fas fa-chart-line"></i> 活跃率: {{ ((active_content_count / content_count * 100) | round(1) if content_count > 0 else 0) }}%
|
<i class="fas fa-check-circle"></i> 完成率: {{ ((completed_todo_count / todo_count * 100) | round(1) if todo_count > 0 else 0) }}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xl-3 col-md-6 mb-4">
|
||||||
|
<div class="card card-stats bg-danger text-white shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-5">
|
||||||
|
<div class="icon-big text-center">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-7">
|
||||||
|
<div class="numbers">
|
||||||
|
<p class="card-category">待完成</p>
|
||||||
|
<h3 class="card-title">{{ pending_todo_count }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="stats">
|
||||||
|
<i class="fas fa-clock"></i> 待处理任务
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -172,36 +197,44 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 最近创建的内容 -->
|
<!-- 最近待办事项 -->
|
||||||
<div class="col-lg-6 mb-4">
|
<div class="col-lg-6 mb-4">
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
<div class="card-header bg-white py-3 d-flex flex-row align-items-center justify-content-between">
|
<div class="card-header bg-white py-3 d-flex flex-row align-items-center justify-content-between">
|
||||||
<h6 class="m-0 font-weight-bold text-primary">
|
<h6 class="m-0 font-weight-bold text-primary">
|
||||||
<i class="fas fa-clock me-2"></i>最近创建的内容
|
<i class="fas fa-tasks me-2"></i>最近待办事项
|
||||||
</h6>
|
</h6>
|
||||||
<a href="/admin/contents" class="btn btn-sm btn-primary">
|
<a href="/admin/todos" class="btn btn-sm btn-primary">
|
||||||
<i class="fas fa-list me-1"></i>查看全部
|
<i class="fas fa-list me-1"></i>查看全部
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if recent_contents %}
|
{% if recent_todos %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th><i class="fas fa-barcode me-1"></i>设备ID</th>
|
|
||||||
<th><i class="fas fa-heading me-1"></i>标题</th>
|
<th><i class="fas fa-heading me-1"></i>标题</th>
|
||||||
<th><i class="fas fa-code-branch me-1"></i>版本</th>
|
<th><i class="fas fa-mobile-alt me-1"></i>设备</th>
|
||||||
|
<th><i class="fas fa-check me-1"></i>状态</th>
|
||||||
<th><i class="fas fa-calendar me-1"></i>创建时间</th>
|
<th><i class="fas fa-calendar me-1"></i>创建时间</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for content in recent_contents %}
|
{% for item in recent_todos %}
|
||||||
|
{% set todo = item.todo %}
|
||||||
|
{% set device = item.device %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/admin/devices/{{ content.device_id }}" class="text-decoration-none"><code>{{ content.device_id }}</code></a></td>
|
<td><a href="/admin/todos/{{ todo.id }}" class="text-decoration-none">{{ todo.title }}</a></td>
|
||||||
<td><a href="/admin/devices/{{ content.device_id }}/contents/{{ content.version }}" class="text-decoration-none">{{ content.title }}</a></td>
|
<td><a href="/admin/devices/{{ device.device_id }}" class="text-decoration-none">{{ device.name or device.device_id }}</a></td>
|
||||||
<td><span class="badge bg-info">v{{ content.version }}</span></td>
|
<td>
|
||||||
<td>{{ content.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
{% if todo.is_completed %}
|
||||||
|
<span class="badge bg-success"><i class="fas fa-check me-1"></i>已完成</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-warning"><i class="fas fa-clock me-1"></i>待完成</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ todo.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -209,8 +242,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-center py-4">
|
<div class="text-center py-4">
|
||||||
<i class="fas fa-inbox fa-3x text-muted mb-3"></i>
|
<i class="fas fa-clipboard-list fa-3x text-muted mb-3"></i>
|
||||||
<p class="text-muted">暂无内容</p>
|
<p class="text-muted">暂无待办事项</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -240,13 +273,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<a href="/admin/upload" class="btn btn-success btn-lg btn-block">
|
<a href="/admin/todos/add" class="btn btn-warning btn-lg btn-block">
|
||||||
<i class="fas fa-upload me-2"></i> 上传图片
|
<i class="fas fa-plus me-2"></i> 添加待办
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<a href="/admin/devices" class="btn btn-warning btn-lg btn-block">
|
<a href="/admin/todos" class="btn btn-success btn-lg btn-block">
|
||||||
<i class="fas fa-tv me-2"></i> 设备管理
|
<i class="fas fa-tasks me-2"></i> 待办管理
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
74
templates/admin/login.html
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}管理员登录{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="login-card">
|
||||||
|
<div class="login-header">
|
||||||
|
<h2>管理员登录</h2>
|
||||||
|
<p class="text-muted">墨水屏桌面屏幕系统</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if error %}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="post" action="/admin/login">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username" class="form-label">用户名</label>
|
||||||
|
<input type="text" class="form-control" id="username" name="username" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">密码</label>
|
||||||
|
<input type="password" class="form-control" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="remember" name="remember">
|
||||||
|
<label class="form-check-label" for="remember">记住我</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary w-100">登录</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.login-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 2rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header h2 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
108
templates/admin/todo_add.html
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}添加待办事项{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||||
|
<h1 class="h2">
|
||||||
|
<i class="fas fa-plus me-2"></i>添加待办事项
|
||||||
|
</h1>
|
||||||
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
|
<div class="btn-group me-2">
|
||||||
|
<a href="/admin/todos{% if device_id %}?device_id={{ device_id }}{% endif %}" class="btn btn-sm btn-outline-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i>返回列表
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/admin">首页</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="/admin/todos{% if device_id %}?device_id={{ device_id }}{% endif %}">待办事项管理</a></li>
|
||||||
|
<li class="breadcrumb-item active">添加待办事项</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-clipboard-list me-2"></i>待办事项信息
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post" action="/admin/todos/add">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="title" class="form-label">标题 <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" id="title" name="title" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="description" class="form-label">描述</label>
|
||||||
|
<textarea class="form-control" id="description" name="description" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="device_id" class="form-label">关联设备 <span class="text-danger">*</span></label>
|
||||||
|
<select class="form-select" id="device_id" name="device_id" required>
|
||||||
|
<option value="">请选择设备</option>
|
||||||
|
{% for device in devices %}
|
||||||
|
<option value="{{ device.device_id }}" {% if device_id and device.device_id == device_id %}selected{% endif %}>
|
||||||
|
{{ device.name or device.device_id }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="due_date" class="form-label">截止时间</label>
|
||||||
|
<input type="datetime-local" class="form-control" id="due_date" name="due_date">
|
||||||
|
<div class="form-text">留空表示无截止时间</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="is_completed" name="is_completed">
|
||||||
|
<label class="form-check-label" for="is_completed">
|
||||||
|
标记为已完成
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<a href="/admin/todos{% if device_id %}?device_id={{ device_id }}{% endif %}" class="btn btn-secondary">
|
||||||
|
<i class="fas fa-times me-1"></i>取消
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save me-1"></i>保存
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>帮助信息
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p>创建一个新的待办事项:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>标题:</strong>待办事项的简短描述</li>
|
||||||
|
<li><strong>描述:</strong>待办事项的详细说明(可选)</li>
|
||||||
|
<li><strong>关联设备:</strong>选择要显示此待办事项的设备</li>
|
||||||
|
<li><strong>截止时间:</strong>设置待办事项的截止时间(可选)</li>
|
||||||
|
<li><strong>状态:</strong>可以选择直接标记为已完成</li>
|
||||||
|
</ul>
|
||||||
|
<p class="text-muted">创建后,待办事项将发送到关联的设备,并可以在设备上标记完成状态。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
161
templates/admin/todo_detail.html
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}待办事项详情{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||||
|
<h1 class="h2">
|
||||||
|
<i class="fas fa-tasks me-2"></i>待办事项详情
|
||||||
|
</h1>
|
||||||
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
|
<div class="btn-group me-2">
|
||||||
|
<a href="/admin/todos{% if todo.device_id %}?device_id={{ todo.device_id }}{% endif %}" class="btn btn-sm btn-outline-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i>返回列表
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/admin">首页</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="/admin/todos{% if todo.device_id %}?device_id={{ todo.device_id }}{% endif %}">待办事项管理</a></li>
|
||||||
|
<li class="breadcrumb-item active">{{ todo.title }}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-clipboard-list me-2"></i>待办事项信息
|
||||||
|
</h5>
|
||||||
|
<span class="badge {% if todo.is_completed %}bg-success{% else %}bg-warning{% %} fs-6">
|
||||||
|
{% if todo.is_completed %}已完成{% else %}未完成{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-3 fw-bold">标题:</div>
|
||||||
|
<div class="col-sm-9">{{ todo.title }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-3 fw-bold">描述:</div>
|
||||||
|
<div class="col-sm-9">{{ todo.description or '无' }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-3 fw-bold">关联设备:</div>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<a href="/admin/devices/{{ device.device_id }}" class="text-decoration-none">
|
||||||
|
{{ device.name or device.device_id }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-3 fw-bold">截止时间:</div>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
{% if todo.due_date %}
|
||||||
|
{{ todo.due_date.strftime('%Y-%m-%d %H:%M') }}
|
||||||
|
{% else %}
|
||||||
|
无
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-3 fw-bold">创建时间:</div>
|
||||||
|
<div class="col-sm-9">{{ todo.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-3 fw-bold">更新时间:</div>
|
||||||
|
<div class="col-sm-9">{{ todo.updated_at.strftime('%Y-%m-%d %H:%M:%S') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if todo.is_completed and todo.completed_at %}
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-3 fw-bold">完成时间:</div>
|
||||||
|
<div class="col-sm-9">{{ todo.completed_at.strftime('%Y-%m-%d %H:%M:%S') }}</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="/admin/todos/{{ todo.id }}/edit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-edit me-1"></i>编辑
|
||||||
|
</a>
|
||||||
|
<form method="post" action="/admin/todos/{{ todo.id }}/toggle" style="display: inline;">
|
||||||
|
<button type="submit" class="btn {% if todo.is_completed %}btn-warning{% else %}btn-success{% %}">
|
||||||
|
<i class="fas {% if todo.is_completed %}fa-undo{% else %}fa-check{% %} me-1"></i>
|
||||||
|
{% if todo.is_completed %}标记为未完成{% else %}标记为已完成{% endif %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<form method="post" action="/admin/todos/{{ todo.id }}/delete" style="display: inline;" onsubmit="return confirm('确定要删除这个待办事项吗?');">
|
||||||
|
<button type="submit" class="btn btn-danger">
|
||||||
|
<i class="fas fa-trash me-1"></i>删除
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>设备信息
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-4 fw-bold">设备ID:</div>
|
||||||
|
<div class="col-sm-8">{{ device.device_id }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-4 fw-bold">设备名称:</div>
|
||||||
|
<div class="col-sm-8">{{ device.name or '未设置' }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-4 fw-bold">设备类型:</div>
|
||||||
|
<div class="col-sm-8">{{ device.device_type or '未知' }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-4 fw-bold">状态:</div>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
{% if device.status == 'online' %}
|
||||||
|
<span class="badge bg-success">在线</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-danger">离线</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-4 fw-bold">最后活跃:</div>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
{% if device.last_seen %}
|
||||||
|
{{ device.last_seen.strftime('%Y-%m-%d %H:%M:%S') }}
|
||||||
|
{% else %}
|
||||||
|
未知
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center mt-3">
|
||||||
|
<a href="/admin/devices/{{ device.device_id }}" class="btn btn-outline-primary btn-sm">
|
||||||
|
<i class="fas fa-tv me-1"></i>查看设备详情
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
121
templates/admin/todo_edit.html
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}编辑待办事项{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||||
|
<h1 class="h2">
|
||||||
|
<i class="fas fa-edit me-2"></i>编辑待办事项
|
||||||
|
</h1>
|
||||||
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
|
<div class="btn-group me-2">
|
||||||
|
<a href="/admin/todos/{{ todo.id }}" class="btn btn-sm btn-outline-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i>返回详情
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/admin">首页</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="/admin/todos{% if todo.device_id %}?device_id={{ todo.device_id }}{% endif %}">待办事项管理</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="/admin/todos/{{ todo.id }}">{{ todo.title }}</a></li>
|
||||||
|
<li class="breadcrumb-item active">编辑</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-clipboard-list me-2"></i>待办事项信息
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post" action="/admin/todos/{{ todo.id }}/edit">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="title" class="form-label">标题 <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" id="title" name="title" value="{{ todo.title }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="description" class="form-label">描述</label>
|
||||||
|
<textarea class="form-control" id="description" name="description" rows="3">{{ todo.description or '' }}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="device_id" class="form-label">关联设备 <span class="text-danger">*</span></label>
|
||||||
|
<select class="form-select" id="device_id" name="device_id" required>
|
||||||
|
{% for device in devices %}
|
||||||
|
<option value="{{ device.device_id }}" {% if device.device_id == todo.device_id %}selected{% endif %}>
|
||||||
|
{{ device.name or device.device_id }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="due_date" class="form-label">截止时间</label>
|
||||||
|
<input type="datetime-local" class="form-control" id="due_date" name="due_date"
|
||||||
|
{% if todo.due_date %}value="{{ todo.due_date.strftime('%Y-%m-%dT%H:%M') }}"{% endif %}>
|
||||||
|
<div class="form-text">留空表示无截止时间</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="is_completed" name="is_completed"
|
||||||
|
{% if todo.is_completed %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="is_completed">
|
||||||
|
标记为已完成
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<a href="/admin/todos/{{ todo.id }}" class="btn btn-secondary">
|
||||||
|
<i class="fas fa-times me-1"></i>取消
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save me-1"></i>保存更改
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>当前信息
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-sm-4 fw-bold">创建时间:</div>
|
||||||
|
<div class="col-sm-8">{{ todo.created_at.strftime('%Y-%m-%d %H:%M') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-sm-4 fw-bold">更新时间:</div>
|
||||||
|
<div class="col-sm-8">{{ todo.updated_at.strftime('%Y-%m-%d %H:%M') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if todo.is_completed and todo.completed_at %}
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-sm-4 fw-bold">完成时间:</div>
|
||||||
|
<div class="col-sm-8">{{ todo.completed_at.strftime('%Y-%m-%d %H:%M') }}</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<p class="text-muted">修改待办事项后,更新将发送到关联的设备。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
218
templates/admin/todos.html
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}待办事项管理{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||||
|
<h1 class="h2">
|
||||||
|
<i class="fas fa-tasks me-2"></i>待办事项管理
|
||||||
|
{% if filtered %}
|
||||||
|
<small class="text-muted">- {{ device.name or device.device_id }}</small>
|
||||||
|
{% endif %}
|
||||||
|
</h1>
|
||||||
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
|
<div class="btn-group me-2">
|
||||||
|
<a href="/admin/todos/add{% if filtered %}?device_id={{ device.device_id }}{% endif %}" class="btn btn-sm btn-outline-secondary">
|
||||||
|
<i class="fas fa-plus me-1"></i>添加待办事项
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% if not filtered %}
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="fas fa-filter me-1"></i>筛选设备
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
{% for device in devices %}
|
||||||
|
<li><a class="dropdown-item" href="/admin/todos?device_id={{ device.device_id }}">{{ device.name or device.device_id }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if filtered %}
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/admin/todos">所有待办事项</a></li>
|
||||||
|
<li class="breadcrumb-item active">{{ device.name or device.device_id }}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
{% if filtered %}
|
||||||
|
{% if todos %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="fas fa-list me-2"></i>待办事项列表
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>标题</th>
|
||||||
|
<th>描述</th>
|
||||||
|
<th>截止时间</th>
|
||||||
|
<th>状态</th>
|
||||||
|
<th>创建时间</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for todo in todos %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/admin/todos/{{ todo.id }}" class="text-decoration-none">
|
||||||
|
{{ todo.title }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ todo.description or '-' }}</td>
|
||||||
|
<td>
|
||||||
|
{% if todo.due_date %}
|
||||||
|
{{ todo.due_date.strftime('%Y-%m-%d %H:%M') }}
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if todo.is_completed %}
|
||||||
|
<span class="badge bg-success">已完成</span>
|
||||||
|
{% if todo.completed_at %}
|
||||||
|
<small class="text-muted d-block">{{ todo.completed_at.strftime('%Y-%m-%d %H:%M') }}</small>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-warning">未完成</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ todo.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
|
<a href="/admin/todos/{{ todo.id }}" class="btn btn-outline-primary" title="查看详情">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
</a>
|
||||||
|
<form method="post" action="/admin/todos/{{ todo.id }}/toggle" style="display: inline;">
|
||||||
|
<button type="submit" class="btn {% if todo.is_completed %}btn-outline-warning{% else %}btn-outline-success{% endif %}" title="{% if todo.is_completed %}标记为未完成{% else %}标记为已完成{% endif %}">
|
||||||
|
<i class="fas {% if todo.is_completed %}fa-undo{% else %}fa-check{% endif %}"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<form method="post" action="/admin/todos/{{ todo.id }}/delete" style="display: inline;" onsubmit="return confirm('确定要删除这个待办事项吗?');">
|
||||||
|
<button type="submit" class="btn btn-outline-danger" title="删除">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body text-center py-5">
|
||||||
|
<i class="fas fa-clipboard-list fa-3x text-muted mb-3"></i>
|
||||||
|
<h5 class="text-muted">该设备暂无待办事项</h5>
|
||||||
|
<p class="text-muted">点击上方按钮添加第一个待办事项</p>
|
||||||
|
<a href="/admin/todos/add?device_id={{ device.device_id }}" class="btn btn-primary">
|
||||||
|
<i class="fas fa-plus me-1"></i>添加待办事项
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% if todo_list %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="fas fa-list me-2"></i>所有待办事项
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>标题</th>
|
||||||
|
<th>设备</th>
|
||||||
|
<th>描述</th>
|
||||||
|
<th>截止时间</th>
|
||||||
|
<th>状态</th>
|
||||||
|
<th>创建时间</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in todo_list %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/admin/todos/{{ item.todo.id }}" class="text-decoration-none">
|
||||||
|
{{ item.todo.title }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="/admin/devices/{{ item.device.device_id }}" class="text-decoration-none">
|
||||||
|
{{ item.device.name or item.device.device_id }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ item.todo.description or '-' }}</td>
|
||||||
|
<td>
|
||||||
|
{% if item.todo.due_date %}
|
||||||
|
{{ item.todo.due_date.strftime('%Y-%m-%d %H:%M') }}
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if item.todo.is_completed %}
|
||||||
|
<span class="badge bg-success">已完成</span>
|
||||||
|
{% if item.todo.completed_at %}
|
||||||
|
<small class="text-muted d-block">{{ item.todo.completed_at.strftime('%Y-%m-%d %H:%M') }}</small>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-warning">未完成</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ item.todo.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
|
<a href="/admin/todos/{{ item.todo.id }}" class="btn btn-outline-primary" title="查看详情">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
</a>
|
||||||
|
<form method="post" action="/admin/todos/{{ item.todo.id }}/toggle" style="display: inline;">
|
||||||
|
<button type="submit" class="btn {% if item.todo.is_completed %}btn-outline-warning{% else %}btn-outline-success{% endif %}" title="{% if item.todo.is_completed %}标记为未完成{% else %}标记为已完成{% endif %}">
|
||||||
|
<i class="fas {% if item.todo.is_completed %}fa-undo{% else %}fa-check{% endif %}"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<form method="post" action="/admin/todos/{{ item.todo.id }}/delete" style="display: inline;" onsubmit="return confirm('确定要删除这个待办事项吗?');">
|
||||||
|
<button type="submit" class="btn btn-outline-danger" title="删除">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body text-center py-5">
|
||||||
|
<i class="fas fa-clipboard-list fa-3x text-muted mb-3"></i>
|
||||||
|
<h5 class="text-muted">暂无待办事项</h5>
|
||||||
|
<p class="text-muted">点击上方按钮添加第一个待办事项</p>
|
||||||
|
<a href="/admin/todos/add" class="btn btn-primary">
|
||||||
|
<i class="fas fa-plus me-1"></i>添加待办事项
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
BIN
test_image.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
167
tool/IMAGE_CONVERTER_README.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# 图片转换工具使用指南
|
||||||
|
|
||||||
|
这个工具可以将多种格式的图片转换为适用于ESP32墨水屏显示的二进制点阵数据。
|
||||||
|
|
||||||
|
## 功能特点
|
||||||
|
|
||||||
|
- 支持多种图片格式:JPG、PNG、BMP、GIF、TIFF等
|
||||||
|
- 自动调整图片大小并居中显示
|
||||||
|
- 支持黑色背景白色文本或白色背景黑色文本
|
||||||
|
- 支持旋转90度显示
|
||||||
|
- 支持Floyd-Steinberg抖动算法提高图像质量
|
||||||
|
- 支持创建文本图像
|
||||||
|
- 支持批量转换
|
||||||
|
|
||||||
|
## 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install Pillow
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 1. 转换单个图片
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 image_converter.py image <输入图片路径> <输出文件路径> [选项]
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "/Users/jeremygan/Desktop/
|
||||||
|
TangledupAI/esp32_GDEY042T81-24Pin-_dirver-" && python3 tool/image_con
|
||||||
|
verter.py image tool/test_iamge_white_bg2.png images/test_image2.py --
|
||||||
|
width 400 --height 300
|
||||||
|
```
|
||||||
|
选项参数:
|
||||||
|
- `--width`: 目标宽度(默认400)
|
||||||
|
- `--height`: 目标高度(默认300)
|
||||||
|
- `--invert`: 反转颜色(白色背景黑色文本)
|
||||||
|
- `--rotate`: 旋转90度
|
||||||
|
- `--no-dither`: 不使用抖动算法
|
||||||
|
|
||||||
|
示例:
|
||||||
|
```bash
|
||||||
|
# 转换为黑色背景白色文本(默认)
|
||||||
|
python3 image_converter.py image photo.jpg photo_dark.py
|
||||||
|
|
||||||
|
# 转换为白色背景黑色文本
|
||||||
|
python3 image_converter.py image photo.jpg photo_light.py --invert
|
||||||
|
|
||||||
|
# 转换并旋转90度
|
||||||
|
python3 image_converter.py image photo.jpg photo_rotated.py --rotate
|
||||||
|
|
||||||
|
# 转换为128x296尺寸并旋转
|
||||||
|
python3 image_converter.py image photo.jpg photo_small.py --width 128 --height 296 --rotate
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 创建文本图像
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 image_converter.py text "<文本内容>" <输出文件路径> [选项]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
选项参数:
|
||||||
|
- `--width`: 目标宽度(默认400)
|
||||||
|
- `--height`: 目标高度(默认300)
|
||||||
|
- `--font-size`: 字体大小(默认24)
|
||||||
|
- `--invert`: 反转颜色(白色背景黑色文本)
|
||||||
|
- `--rotate`: 旋转90度
|
||||||
|
|
||||||
|
示例:
|
||||||
|
```bash
|
||||||
|
# 创建文本图像
|
||||||
|
python3 image_converter.py text "Hello ESP32!" hello.py
|
||||||
|
|
||||||
|
# 创建大字体文本图像
|
||||||
|
python3 image_converter.py text "Hello ESP32!" hello_big.py --font-size 32
|
||||||
|
|
||||||
|
# 创建旋转文本图像
|
||||||
|
python3 image_converter.py text "Rotated Text" rotated.py --rotate
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 批量转换
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 image_converter.py batch <输入目录> <输出目录> [选项]
|
||||||
|
```
|
||||||
|
|
||||||
|
示例:
|
||||||
|
```bash
|
||||||
|
# 批量转换目录中的所有图片
|
||||||
|
python3 image_converter.py batch images/ converted/
|
||||||
|
|
||||||
|
# 批量转换为白色背景黑色文本
|
||||||
|
python3 image_converter.py batch images/ converted/ --invert
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输出格式
|
||||||
|
|
||||||
|
转换后的文件包含一个bytearray变量,变量名与文件名相同:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Converted from photo.jpg
|
||||||
|
# Size: 400x300
|
||||||
|
# Inverted: False, Rotated: False
|
||||||
|
photo = bytearray(b'\x00\x00\x00\x0...')
|
||||||
|
```
|
||||||
|
|
||||||
|
## 在ESP32项目中使用
|
||||||
|
|
||||||
|
1. 将转换后的.py文件复制到ESP32项目目录
|
||||||
|
2. 在boot.py中设置`RUN_MODE=3`
|
||||||
|
3. 修改image.py导入转换后的图片数据:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 在image.py中导入转换后的图片
|
||||||
|
from photo import photo
|
||||||
|
|
||||||
|
def run(img_data=None):
|
||||||
|
if img_data is None:
|
||||||
|
# 使用默认图片
|
||||||
|
img_data = photo
|
||||||
|
|
||||||
|
# ... 其余代码保持不变
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 上传代码到ESP32并运行
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
运行示例脚本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 convert_examples.py
|
||||||
|
```
|
||||||
|
|
||||||
|
这将创建示例图片目录并展示各种转换用法。
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 图片会自动调整大小并居中显示,保持原始宽高比
|
||||||
|
2. 使用抖动算法可以提高图像质量,但会增加文件大小
|
||||||
|
3. 旋转90度后,宽度和高度会互换
|
||||||
|
4. 确保输出目录有写入权限
|
||||||
|
5. 转换大图片可能需要一些时间
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
1. **ImportError: No module named 'PIL'**
|
||||||
|
- 解决方案:运行 `pip install Pillow`
|
||||||
|
|
||||||
|
2. **无法识别的图片格式**
|
||||||
|
- 解决方案:确保图片格式受支持(JPG、PNG、BMP、GIF、TIFF)
|
||||||
|
|
||||||
|
3. **转换后的图片显示不正确**
|
||||||
|
- 解决方案:尝试使用`--invert`参数反转颜色
|
||||||
|
- 尝试使用`--rotate`参数旋转图片
|
||||||
|
|
||||||
|
4. **内存不足**
|
||||||
|
- 解决方案:减小目标尺寸或使用更小的源图片
|
||||||
|
|
||||||
|
## 技术细节
|
||||||
|
|
||||||
|
- 图片使用Floyd-Steinberg抖动算法转换为1位黑白图像
|
||||||
|
- 每个字节表示8个像素,最高位在前
|
||||||
|
- 像素值0表示黑色,1表示白色
|
||||||
|
- 字节数组按行组织,每行需要的字节数为(宽度+7)//8
|
||||||
144
tool/USAGE_CN.md
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# 图片转换工具使用说明
|
||||||
|
|
||||||
|
## 问题修复说明
|
||||||
|
|
||||||
|
### 之前的问题
|
||||||
|
转换器在生成的图片数据中错误地插入了换行符 (`\n`, 0x0A),导致墨水屏显示乱码。
|
||||||
|
|
||||||
|
### 修复内容
|
||||||
|
修改了 `image_converter.py` 中的字节数组格式化逻辑:
|
||||||
|
- **之前**: 每16个字节后直接写入 `\n`,导致换行符成为数据的一部分
|
||||||
|
- **现在**: 每16个字节后正确地分割字符串为多个 `b'...'` 部分
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 1. 转换单个图片
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python image_converter.py image <输入图片路径> <输出文件路径> [选项]
|
||||||
|
```
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
```bash
|
||||||
|
# 基本用法
|
||||||
|
python image_converter.py image test_image.png output.py
|
||||||
|
|
||||||
|
# 指定尺寸
|
||||||
|
python image_converter.py image test_image.png output.py --width 400 --height 300
|
||||||
|
|
||||||
|
# 反转颜色(白底黑字)
|
||||||
|
python image_converter.py image test_image.png output.py --invert
|
||||||
|
|
||||||
|
# 旋转90度
|
||||||
|
python image_converter.py image test_image.png output.py --rotate
|
||||||
|
|
||||||
|
# 不使用抖动算法
|
||||||
|
python image_converter.py image test_image.png output.py --no-dither
|
||||||
|
```
|
||||||
|
|
||||||
|
**重要提示:**
|
||||||
|
- 输出路径必须包含文件名,不能只是目录
|
||||||
|
- ❌ 错误: `python image_converter.py image input.png /path/to/directory`
|
||||||
|
- ✅ 正确: `python image_converter.py image input.png /path/to/directory/output.py`
|
||||||
|
|
||||||
|
### 2. 创建文本图片
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python image_converter.py text <文本内容> <输出文件路径> [选项]
|
||||||
|
```
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
```bash
|
||||||
|
# 基本用法
|
||||||
|
python image_converter.py text "Hello World" output.py
|
||||||
|
|
||||||
|
# 指定字体大小
|
||||||
|
python image_converter.py text "Hello" output.py --font-size 32
|
||||||
|
|
||||||
|
# 白底黑字
|
||||||
|
python image_converter.py text "Hello" output.py --invert
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 批量转换
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python image_converter.py batch <输入目录> <输出目录> [选项]
|
||||||
|
```
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
```bash
|
||||||
|
python image_converter.py batch ./input_images ./output_files --width 400 --height 300
|
||||||
|
```
|
||||||
|
|
||||||
|
## 参数说明
|
||||||
|
|
||||||
|
### 通用参数
|
||||||
|
- `--width`: 目标宽度(默认400像素)
|
||||||
|
- `--height`: 目标高度(默认300像素)
|
||||||
|
- `--invert`: 反转颜色(黑底白字 → 白底黑字)
|
||||||
|
- `--rotate`: 旋转90度
|
||||||
|
|
||||||
|
### 图片转换专用
|
||||||
|
- `--no-dither`: 不使用抖动算法(使用简单阈值二值化)
|
||||||
|
|
||||||
|
### 文本创建专用
|
||||||
|
- `--font-size`: 字体大小(默认24)
|
||||||
|
|
||||||
|
## 在MicroPython中使用生成的文件
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 导入生成的图片数据
|
||||||
|
from output import image_data
|
||||||
|
|
||||||
|
# 在image.py中使用
|
||||||
|
import image
|
||||||
|
image.run(image_data, width=400, height=300)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 验证修复
|
||||||
|
|
||||||
|
运行验证脚本检查生成的文件是否正确:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python verify_fix.py
|
||||||
|
```
|
||||||
|
|
||||||
|
这会比较修复前后的数据,确认没有错误的换行符。
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q: 为什么显示乱码?
|
||||||
|
A: 如果使用旧版本转换器生成的文件,数据中包含错误的换行符。请使用修复后的转换器重新生成。
|
||||||
|
|
||||||
|
### Q: 如何重新生成所有图片?
|
||||||
|
A: 使用批量转换功能:
|
||||||
|
```bash
|
||||||
|
python image_converter.py batch ./old_images ./new_images --width 400 --height 300
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q: 支持哪些图片格式?
|
||||||
|
A: 支持 JPG, PNG, BMP, GIF, TIFF 等常见格式。
|
||||||
|
|
||||||
|
### Q: 生成的文件大小是多少?
|
||||||
|
A: 对于400x300的图片,二进制数据大小为 15000 字节 (400 * 300 / 8)。
|
||||||
|
|
||||||
|
## 技术细节
|
||||||
|
|
||||||
|
### 数据格式
|
||||||
|
- 每个像素用1位表示(黑或白)
|
||||||
|
- 每行需要 `(width + 7) // 8` 个字节
|
||||||
|
- 总字节数: `width_bytes * height`
|
||||||
|
- 位顺序: MSB first (最高位在前)
|
||||||
|
|
||||||
|
### 正确的输出格式
|
||||||
|
```python
|
||||||
|
image_data = bytearray(b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'...')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 错误的输出格式(已修复)
|
||||||
|
```python
|
||||||
|
# 不要使用这种格式!
|
||||||
|
image_data = bytearray(b'\n\xFF\xFF\xFF\xFF...\n\xFF\xFF...')
|
||||||
|
```
|
||||||
BIN
tool/__pycache__/image_converter.cpython-312.pyc
Normal file
BIN
tool/__pycache__/image_converter.cpython-313.pyc
Normal file
123
tool/convert_examples.py
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
图片转换工具使用示例
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def run_command(cmd):
|
||||||
|
"""运行命令并打印输出"""
|
||||||
|
print(f"执行命令: {' '.join(cmd)}")
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print(f"错误: {result.stderr}")
|
||||||
|
return result.returncode == 0
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=== 图片转换工具使用示例 ===\n")
|
||||||
|
|
||||||
|
# 检查是否存在示例图片目录
|
||||||
|
example_dir = "example_images"
|
||||||
|
output_dir = "converted_images"
|
||||||
|
|
||||||
|
if not os.path.exists(example_dir):
|
||||||
|
print(f"创建示例图片目录: {example_dir}")
|
||||||
|
os.makedirs(example_dir, exist_ok=True)
|
||||||
|
print("请将图片文件放入此目录后再次运行此脚本")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not os.path.exists(output_dir):
|
||||||
|
print(f"创建输出目录: {output_dir}")
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 示例1: 转换单个图片为黑色背景白色文本
|
||||||
|
print("\n示例1: 转换单个图片为黑色背景白色文本")
|
||||||
|
print("=" * 50)
|
||||||
|
cmd = [
|
||||||
|
"python3", "image_converter.py", "image",
|
||||||
|
os.path.join(example_dir, "example.jpg"),
|
||||||
|
os.path.join(output_dir, "example_dark.py"),
|
||||||
|
"--width", "400",
|
||||||
|
"--height", "300"
|
||||||
|
]
|
||||||
|
run_command(cmd)
|
||||||
|
|
||||||
|
# 示例2: 转换单个图片为白色背景黑色文本
|
||||||
|
print("\n示例2: 转换单个图片为白色背景黑色文本")
|
||||||
|
print("=" * 50)
|
||||||
|
cmd = [
|
||||||
|
"python3", "image_converter.py", "image",
|
||||||
|
os.path.join(example_dir, "example.jpg"),
|
||||||
|
os.path.join(output_dir, "example_light.py"),
|
||||||
|
"--width", "400",
|
||||||
|
"--height", "300",
|
||||||
|
"--invert"
|
||||||
|
]
|
||||||
|
run_command(cmd)
|
||||||
|
|
||||||
|
# 示例3: 创建文本图像
|
||||||
|
print("\n示例3: 创建文本图像")
|
||||||
|
print("=" * 50)
|
||||||
|
cmd = [
|
||||||
|
"python3", "image_converter.py", "text",
|
||||||
|
"Hello ESP32!",
|
||||||
|
os.path.join(output_dir, "hello_text.py"),
|
||||||
|
"--width", "400",
|
||||||
|
"--height", "300",
|
||||||
|
"--font-size", "32"
|
||||||
|
]
|
||||||
|
run_command(cmd)
|
||||||
|
|
||||||
|
# 示例4: 创建旋转90度的文本图像
|
||||||
|
print("\n示例4: 创建旋转90度的文本图像")
|
||||||
|
print("=" * 50)
|
||||||
|
cmd = [
|
||||||
|
"python3", "image_converter.py", "text",
|
||||||
|
"Rotated Text",
|
||||||
|
os.path.join(output_dir, "rotated_text.py"),
|
||||||
|
"--width", "400",
|
||||||
|
"--height", "300",
|
||||||
|
"--font-size", "32",
|
||||||
|
"--rotate"
|
||||||
|
]
|
||||||
|
run_command(cmd)
|
||||||
|
|
||||||
|
# 示例5: 批量转换图片
|
||||||
|
print("\n示例5: 批量转换图片")
|
||||||
|
print("=" * 50)
|
||||||
|
cmd = [
|
||||||
|
"python3", "image_converter.py", "batch",
|
||||||
|
example_dir,
|
||||||
|
output_dir,
|
||||||
|
"--width", "400",
|
||||||
|
"--height", "300"
|
||||||
|
]
|
||||||
|
run_command(cmd)
|
||||||
|
|
||||||
|
# 示例6: 转换为128x296尺寸(与现有示例相同)
|
||||||
|
print("\n示例6: 转换为128x296尺寸")
|
||||||
|
print("=" * 50)
|
||||||
|
cmd = [
|
||||||
|
"python3", "image_converter.py", "image",
|
||||||
|
os.path.join(example_dir, "example.jpg"),
|
||||||
|
os.path.join(output_dir, "example_128x296.py"),
|
||||||
|
"--width", "128",
|
||||||
|
"--height", "296",
|
||||||
|
"--rotate"
|
||||||
|
]
|
||||||
|
run_command(cmd)
|
||||||
|
|
||||||
|
print("\n=== 转换完成 ===")
|
||||||
|
print(f"输出文件位于: {output_dir}")
|
||||||
|
print("\n使用方法:")
|
||||||
|
print("1. 将转换后的.py文件复制到ESP32项目目录")
|
||||||
|
print("2. 在boot.py中设置RUN_MODE=3")
|
||||||
|
print("3. 修改image.py导入转换后的图片数据")
|
||||||
|
print("4. 上传代码到ESP32并运行")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
166
tool/example_usage.py
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
示例:如何在ESP32项目中使用转换后的图片数据
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 假设你已经使用图片转换工具将图片转换为example_image.py
|
||||||
|
# 其中包含以下内容:
|
||||||
|
# example_image = bytearray(b'\x00\x00\x00\x0...')
|
||||||
|
|
||||||
|
# 1. 导入转换后的图片数据
|
||||||
|
# from example_image import example_image
|
||||||
|
|
||||||
|
# 2. 在image.py中使用图片数据
|
||||||
|
"""
|
||||||
|
# image.py 中的示例代码
|
||||||
|
|
||||||
|
import framebuf
|
||||||
|
from machine import Pin, SPI
|
||||||
|
import epaper4in2
|
||||||
|
import time
|
||||||
|
import config
|
||||||
|
|
||||||
|
def run(img_data=None):
|
||||||
|
# 如果没有提供图片数据,使用默认图片
|
||||||
|
if img_data is None:
|
||||||
|
# 可以在这里设置默认图片
|
||||||
|
img_data = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 初始化墨水屏
|
||||||
|
epd = epaper4in2.EPD()
|
||||||
|
epd.init()
|
||||||
|
|
||||||
|
# 创建帧缓冲区
|
||||||
|
buf = bytearray(config.WIDTH * config.HEIGHT // 8)
|
||||||
|
fb = framebuf.FrameBuffer(buf, config.WIDTH, config.HEIGHT, framebuf.MONO_HLSB)
|
||||||
|
|
||||||
|
if img_data is not None:
|
||||||
|
# 使用提供的图片数据
|
||||||
|
# 根据图片数据长度判断图片尺寸
|
||||||
|
data_length = len(img_data)
|
||||||
|
|
||||||
|
# 常见尺寸检查
|
||||||
|
if data_length == 128 * 296 // 8: # 128x296
|
||||||
|
img_width, img_height = 128, 296
|
||||||
|
elif data_length == 400 * 300 // 8: # 400x300
|
||||||
|
img_width, img_height = 400, 300
|
||||||
|
else:
|
||||||
|
# 默认尺寸
|
||||||
|
img_width, img_height = 400, 300
|
||||||
|
|
||||||
|
# 计算居中位置
|
||||||
|
x_offset = (config.WIDTH - img_width) // 2
|
||||||
|
y_offset = (config.HEIGHT - img_height) // 2
|
||||||
|
|
||||||
|
# 将图片数据复制到帧缓冲区
|
||||||
|
for y in range(img_height):
|
||||||
|
for x in range(img_width):
|
||||||
|
# 计算源位置
|
||||||
|
src_byte_index = y * ((img_width + 7) // 8) + x // 8
|
||||||
|
src_bit_position = 7 - (x % 8)
|
||||||
|
|
||||||
|
# 获取像素值
|
||||||
|
pixel = (img_data[src_byte_index] >> src_bit_position) & 1
|
||||||
|
|
||||||
|
# 计算目标位置
|
||||||
|
dst_x = x_offset + x
|
||||||
|
dst_y = y_offset + y
|
||||||
|
|
||||||
|
if 0 <= dst_x < config.WIDTH and 0 <= dst_y < config.HEIGHT:
|
||||||
|
fb.pixel(dst_x, dst_y, pixel)
|
||||||
|
else:
|
||||||
|
# 如果没有图片数据,显示测试图案
|
||||||
|
fb.fill(0)
|
||||||
|
fb.rect(10, 10, config.WIDTH-20, config.HEIGHT-20, 1)
|
||||||
|
fb.text("No Image Data", 50, config.HEIGHT//2, 1)
|
||||||
|
|
||||||
|
# 显示图片
|
||||||
|
epd.display_frame(buf)
|
||||||
|
|
||||||
|
# 等待一段时间
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
# 清屏并进入深度睡眠
|
||||||
|
epd.clear()
|
||||||
|
epd.sleep()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"显示图片时出错: {e}")
|
||||||
|
# 清屏并进入深度睡眠
|
||||||
|
epd.clear()
|
||||||
|
epd.sleep()
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 3. 在boot.py中设置RUN_MODE=3
|
||||||
|
"""
|
||||||
|
# boot.py 中的示例代码
|
||||||
|
|
||||||
|
import machine
|
||||||
|
import time
|
||||||
|
import config
|
||||||
|
|
||||||
|
# 设置运行模式
|
||||||
|
# 0: 正常运行模式
|
||||||
|
# 1: WiFi连接模式
|
||||||
|
# 2: 配置模式
|
||||||
|
# 3: 图片显示模式
|
||||||
|
RUN_MODE = 3
|
||||||
|
|
||||||
|
if RUN_MODE == 3:
|
||||||
|
# 导入图片数据
|
||||||
|
from example_image import example_image
|
||||||
|
|
||||||
|
# 导入图片显示模块
|
||||||
|
import image
|
||||||
|
|
||||||
|
# 显示图片
|
||||||
|
image.run(example_image)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 4. 使用不同的图片
|
||||||
|
"""
|
||||||
|
# 你可以根据需要导入不同的图片数据
|
||||||
|
|
||||||
|
# 显示黑色背景白色文本的图片
|
||||||
|
from example_dark import example_dark
|
||||||
|
image.run(example_dark)
|
||||||
|
|
||||||
|
# 显示白色背景黑色文本的图片
|
||||||
|
from example_light import example_light
|
||||||
|
image.run(example_light)
|
||||||
|
|
||||||
|
# 显示旋转的图片
|
||||||
|
from example_rotated import example_rotated
|
||||||
|
image.run(example_rotated)
|
||||||
|
|
||||||
|
# 显示文本图片
|
||||||
|
from hello_text import hello_text
|
||||||
|
image.run(hello_text)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 5. 动态选择图片
|
||||||
|
"""
|
||||||
|
# 你可以根据条件动态选择要显示的图片
|
||||||
|
|
||||||
|
def display_image_by_condition(condition):
|
||||||
|
if condition == "dark":
|
||||||
|
from example_dark import example_dark
|
||||||
|
return example_dark
|
||||||
|
elif condition == "light":
|
||||||
|
from example_light import example_light
|
||||||
|
return example_light
|
||||||
|
elif condition == "rotated":
|
||||||
|
from example_rotated import example_rotated
|
||||||
|
return example_rotated
|
||||||
|
else:
|
||||||
|
from hello_text import hello_text
|
||||||
|
return hello_text
|
||||||
|
|
||||||
|
# 使用示例
|
||||||
|
img_data = display_image_by_condition("dark")
|
||||||
|
image.run(img_data)
|
||||||
|
"""
|
||||||
|
|
||||||
|
print("这是一个示例文件,展示了如何在ESP32项目中使用转换后的图片数据。")
|
||||||
|
print("请参考注释中的代码示例,根据你的实际需求进行修改。")
|
||||||
313
tool/image_converter.py
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
图片转换工具 - 将多种格式的图片转换为适用于墨水屏显示的二进制点阵数据
|
||||||
|
支持转换为黑色背景白色文本或白色背景黑色文本的格式
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from PIL import Image, ImageOps, ImageDraw, ImageFont
|
||||||
|
import argparse
|
||||||
|
import math
|
||||||
|
|
||||||
|
def convert_image_to_epaper(input_path, output_path, width=400, height=300, invert=False, rotate=False, dither=True):
|
||||||
|
"""
|
||||||
|
将图片转换为墨水屏二进制格式
|
||||||
|
|
||||||
|
参数:
|
||||||
|
input_path: 输入图片路径
|
||||||
|
output_path: 输出文件路径
|
||||||
|
width: 目标宽度(默认400)
|
||||||
|
height: 目标高度(默认300)
|
||||||
|
invert: 是否反转颜色(默认False,黑色背景白色文本)
|
||||||
|
rotate: 是否旋转90度(默认False)
|
||||||
|
dither: 是否使用抖动算法(默认True)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 打开图片
|
||||||
|
img = Image.open(input_path)
|
||||||
|
|
||||||
|
# 转换为RGB模式
|
||||||
|
if img.mode != 'RGB':
|
||||||
|
img = img.convert('RGB')
|
||||||
|
|
||||||
|
# 调整大小,保持宽高比
|
||||||
|
img_ratio = img.width / img.height
|
||||||
|
target_ratio = width / height
|
||||||
|
|
||||||
|
if img_ratio > target_ratio:
|
||||||
|
# 图片较宽,以宽度为准
|
||||||
|
new_width = width
|
||||||
|
new_height = int(width / img_ratio)
|
||||||
|
else:
|
||||||
|
# 图片较高,以高度为准
|
||||||
|
new_height = height
|
||||||
|
new_width = int(height * img_ratio)
|
||||||
|
|
||||||
|
img = img.resize((new_width, new_height), Image.LANCZOS)
|
||||||
|
|
||||||
|
# 创建目标大小的黑色背景
|
||||||
|
result = Image.new('RGB', (width, height), (0, 0, 0) if not invert else (255, 255, 255))
|
||||||
|
|
||||||
|
# 计算居中位置
|
||||||
|
x_offset = (width - new_width) // 2
|
||||||
|
y_offset = (height - new_height) // 2
|
||||||
|
|
||||||
|
# 将图片粘贴到中心
|
||||||
|
result.paste(img, (x_offset, y_offset))
|
||||||
|
|
||||||
|
# 转换为灰度
|
||||||
|
result = result.convert('L')
|
||||||
|
|
||||||
|
# 转换为1位黑白图像
|
||||||
|
if dither:
|
||||||
|
result = result.convert('1', dither=Image.FLOYDSTEINBERG)
|
||||||
|
else:
|
||||||
|
# 使用阈值128进行二值化
|
||||||
|
result = result.point(lambda x: 0 if x < 128 else 255, '1')
|
||||||
|
|
||||||
|
# 如果需要反转颜色
|
||||||
|
if invert:
|
||||||
|
result = ImageOps.invert(result)
|
||||||
|
|
||||||
|
# 如果需要旋转90度
|
||||||
|
if rotate:
|
||||||
|
result = result.rotate(90, expand=True)
|
||||||
|
# 如果旋转后尺寸不匹配,需要重新调整
|
||||||
|
if result.size != (width, height):
|
||||||
|
result = result.resize((width, height), Image.LANCZOS)
|
||||||
|
|
||||||
|
# 转换为字节数组
|
||||||
|
width_bytes = (width + 7) // 8 # 每行需要的字节数
|
||||||
|
total_bytes = width_bytes * height
|
||||||
|
|
||||||
|
# 创建字节数组
|
||||||
|
byte_array = bytearray(total_bytes)
|
||||||
|
|
||||||
|
# 将像素数据转换为字节数组
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width):
|
||||||
|
# 获取像素值 (0或255)
|
||||||
|
pixel = 0 if result.getpixel((x, y)) == 0 else 1
|
||||||
|
|
||||||
|
# 计算字节位置和位位置
|
||||||
|
byte_index = y * width_bytes + x // 8
|
||||||
|
bit_position = 7 - (x % 8) # 最高位在前
|
||||||
|
|
||||||
|
# 设置位
|
||||||
|
if pixel:
|
||||||
|
byte_array[byte_index] |= (1 << bit_position)
|
||||||
|
|
||||||
|
# 生成Python代码
|
||||||
|
var_name = os.path.splitext(os.path.basename(output_path))[0]
|
||||||
|
|
||||||
|
with open(output_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(f"# Converted from {input_path}\n")
|
||||||
|
f.write(f"# Size: {width}x{height}\n")
|
||||||
|
f.write(f"# Inverted: {invert}, Rotated: {rotate}\n")
|
||||||
|
f.write(f"{var_name} = bytearray(b'")
|
||||||
|
|
||||||
|
# 将字节数组格式化为十六进制字符串
|
||||||
|
for i, byte in enumerate(byte_array):
|
||||||
|
if i > 0 and i % 16 == 0:
|
||||||
|
f.write("'\n b'")
|
||||||
|
f.write(f"\\x{byte:02X}")
|
||||||
|
|
||||||
|
f.write("')\n")
|
||||||
|
|
||||||
|
print(f"转换完成: {input_path} -> {output_path}")
|
||||||
|
print(f"输出尺寸: {width}x{height}")
|
||||||
|
print(f"总字节数: {total_bytes}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"转换失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def create_text_image(text, output_path, width=400, height=300, font_size=24, invert=False, rotate=False):
|
||||||
|
"""
|
||||||
|
创建文本图像并转换为墨水屏二进制格式
|
||||||
|
|
||||||
|
参数:
|
||||||
|
text: 要显示的文本
|
||||||
|
output_path: 输出文件路径
|
||||||
|
width: 目标宽度(默认400)
|
||||||
|
height: 目标高度(默认300)
|
||||||
|
font_size: 字体大小(默认24)
|
||||||
|
invert: 是否反转颜色(默认False,黑色背景白色文本)
|
||||||
|
rotate: 是否旋转90度(默认False)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 创建图像
|
||||||
|
result = Image.new('RGB', (width, height), (0, 0, 0) if not invert else (255, 255, 255))
|
||||||
|
draw = ImageDraw.Draw(result)
|
||||||
|
|
||||||
|
# 尝试加载字体
|
||||||
|
try:
|
||||||
|
# 尝试使用系统字体
|
||||||
|
font = ImageFont.truetype("arial.ttf", font_size)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
# 尝试使用项目中的字体
|
||||||
|
font = ImageFont.truetype("GB2312-12.fon", font_size)
|
||||||
|
except:
|
||||||
|
# 使用默认字体
|
||||||
|
font = ImageFont.load_default()
|
||||||
|
|
||||||
|
# 计算文本位置
|
||||||
|
text_width, text_height = draw.textsize(text, font=font)
|
||||||
|
x = (width - text_width) // 2
|
||||||
|
y = (height - text_height) // 2
|
||||||
|
|
||||||
|
# 绘制文本
|
||||||
|
text_color = (255, 255, 255) if not invert else (0, 0, 0)
|
||||||
|
draw.text((x, y), text, font=font, fill=text_color)
|
||||||
|
|
||||||
|
# 转换为灰度
|
||||||
|
result = result.convert('L')
|
||||||
|
|
||||||
|
# 转换为1位黑白图像
|
||||||
|
result = result.convert('1')
|
||||||
|
|
||||||
|
# 如果需要旋转90度
|
||||||
|
if rotate:
|
||||||
|
result = result.rotate(90, expand=True)
|
||||||
|
# 如果旋转后尺寸不匹配,需要重新调整
|
||||||
|
if result.size != (width, height):
|
||||||
|
result = result.resize((width, height), Image.LANCZOS)
|
||||||
|
|
||||||
|
# 转换为字节数组
|
||||||
|
width_bytes = (width + 7) // 8 # 每行需要的字节数
|
||||||
|
total_bytes = width_bytes * height
|
||||||
|
|
||||||
|
# 创建字节数组
|
||||||
|
byte_array = bytearray(total_bytes)
|
||||||
|
|
||||||
|
# 将像素数据转换为字节数组
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width):
|
||||||
|
# 获取像素值 (0或255)
|
||||||
|
pixel = 0 if result.getpixel((x, y)) == 0 else 1
|
||||||
|
|
||||||
|
# 计算字节位置和位位置
|
||||||
|
byte_index = y * width_bytes + x // 8
|
||||||
|
bit_position = 7 - (x % 8) # 最高位在前
|
||||||
|
|
||||||
|
# 设置位
|
||||||
|
if pixel:
|
||||||
|
byte_array[byte_index] |= (1 << bit_position)
|
||||||
|
|
||||||
|
# 生成Python代码
|
||||||
|
var_name = os.path.splitext(os.path.basename(output_path))[0]
|
||||||
|
|
||||||
|
with open(output_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(f"# Text image: {text}\n")
|
||||||
|
f.write(f"# Size: {width}x{height}\n")
|
||||||
|
f.write(f"# Font size: {font_size}\n")
|
||||||
|
f.write(f"# Inverted: {invert}, Rotated: {rotate}\n")
|
||||||
|
f.write(f"{var_name} = bytearray(b'")
|
||||||
|
|
||||||
|
# 将字节数组格式化为十六进制字符串
|
||||||
|
for i, byte in enumerate(byte_array):
|
||||||
|
if i > 0 and i % 16 == 0:
|
||||||
|
f.write("'\n b'")
|
||||||
|
f.write(f"\\x{byte:02X}")
|
||||||
|
|
||||||
|
f.write("')\n")
|
||||||
|
|
||||||
|
print(f"文本图像创建完成: {output_path}")
|
||||||
|
print(f"文本: {text}")
|
||||||
|
print(f"输出尺寸: {width}x{height}")
|
||||||
|
print(f"总字节数: {total_bytes}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"创建失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description='将图片转换为墨水屏二进制格式')
|
||||||
|
subparsers = parser.add_subparsers(dest='command', help='子命令')
|
||||||
|
|
||||||
|
# 图片转换命令
|
||||||
|
img_parser = subparsers.add_parser('image', help='转换图片文件')
|
||||||
|
img_parser.add_argument('input', help='输入图片路径')
|
||||||
|
img_parser.add_argument('output', help='输出文件路径')
|
||||||
|
img_parser.add_argument('--width', type=int, default=400, help='目标宽度(默认400)')
|
||||||
|
img_parser.add_argument('--height', type=int, default=300, help='目标高度(默认300)')
|
||||||
|
img_parser.add_argument('--invert', action='store_true', help='反转颜色(白色背景黑色文本)')
|
||||||
|
img_parser.add_argument('--rotate', action='store_true', help='旋转90度')
|
||||||
|
img_parser.add_argument('--no-dither', action='store_true', help='不使用抖动算法')
|
||||||
|
|
||||||
|
# 文本创建命令
|
||||||
|
text_parser = subparsers.add_parser('text', help='创建文本图像')
|
||||||
|
text_parser.add_argument('text', help='要显示的文本')
|
||||||
|
text_parser.add_argument('output', help='输出文件路径')
|
||||||
|
text_parser.add_argument('--width', type=int, default=400, help='目标宽度(默认400)')
|
||||||
|
text_parser.add_argument('--height', type=int, default=300, help='目标高度(默认300)')
|
||||||
|
text_parser.add_argument('--font-size', type=int, default=24, help='字体大小(默认24)')
|
||||||
|
text_parser.add_argument('--invert', action='store_true', help='反转颜色(白色背景黑色文本)')
|
||||||
|
text_parser.add_argument('--rotate', action='store_true', help='旋转90度')
|
||||||
|
|
||||||
|
# 批量转换命令
|
||||||
|
batch_parser = subparsers.add_parser('batch', help='批量转换图片')
|
||||||
|
batch_parser.add_argument('input_dir', help='输入目录')
|
||||||
|
batch_parser.add_argument('output_dir', help='输出目录')
|
||||||
|
batch_parser.add_argument('--width', type=int, default=400, help='目标宽度(默认400)')
|
||||||
|
batch_parser.add_argument('--height', type=int, default=300, help='目标高度(默认300)')
|
||||||
|
batch_parser.add_argument('--invert', action='store_true', help='反转颜色(白色背景黑色文本)')
|
||||||
|
batch_parser.add_argument('--rotate', action='store_true', help='旋转90度')
|
||||||
|
batch_parser.add_argument('--no-dither', action='store_true', help='不使用抖动算法')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.command == 'image':
|
||||||
|
convert_image_to_epaper(
|
||||||
|
args.input,
|
||||||
|
args.output,
|
||||||
|
args.width,
|
||||||
|
args.height,
|
||||||
|
args.invert,
|
||||||
|
args.rotate,
|
||||||
|
not args.no_dither
|
||||||
|
)
|
||||||
|
elif args.command == 'text':
|
||||||
|
create_text_image(
|
||||||
|
args.text,
|
||||||
|
args.output,
|
||||||
|
args.width,
|
||||||
|
args.height,
|
||||||
|
args.font_size,
|
||||||
|
args.invert,
|
||||||
|
args.rotate
|
||||||
|
)
|
||||||
|
elif args.command == 'batch':
|
||||||
|
# 确保输出目录存在
|
||||||
|
os.makedirs(args.output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 支持的图片格式
|
||||||
|
supported_formats = ('.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff')
|
||||||
|
|
||||||
|
# 遍历输入目录
|
||||||
|
for filename in os.listdir(args.input_dir):
|
||||||
|
if filename.lower().endswith(supported_formats):
|
||||||
|
input_path = os.path.join(args.input_dir, filename)
|
||||||
|
output_filename = os.path.splitext(filename)[0] + '.py'
|
||||||
|
output_path = os.path.join(args.output_dir, output_filename)
|
||||||
|
|
||||||
|
convert_image_to_epaper(
|
||||||
|
input_path,
|
||||||
|
output_path,
|
||||||
|
args.width,
|
||||||
|
args.height,
|
||||||
|
args.invert,
|
||||||
|
args.rotate,
|
||||||
|
not args.no_dither
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
BIN
tool/test_iamge.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
941
tool/test_iamge_fixed.py
Normal file
@@ -0,0 +1,941 @@
|
|||||||
|
# Converted from tool/test_iamge_white_bg.png
|
||||||
|
# Size: 400x300
|
||||||
|
# Inverted: False, Rotated: False
|
||||||
|
test_iamge_fixed = bytearray(b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\x8F\xFF\xFF\xFF\xFF\xFC\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFC\xFF\x87\xFF\xFF\xFF\xFF\xF8\xFF\x3F\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFC\x7F\x87\xFF\xFF\xFF\xFF\xF8\xF8\x1F\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFD\x3F\x06\x8F\xFF\xFF\xF8\xF8\x40\x8F'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x3A\x10\x07\xFF\xFF\xF8\xF2'
|
||||||
|
b'\x02\x5F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x27\xFF\xFF'
|
||||||
|
b'\xF8\x00\x0B\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE8\x00\x81\x57'
|
||||||
|
b'\xFF\xFF\xF8\x01\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x48'
|
||||||
|
b'\x3F\xFF\xFF\xFC\x81\x11\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC'
|
||||||
|
b'\x00\x02\xFF\xFF\xFF\xF8\x00\x77\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFE\x49\x38\xFF\xFF\xFF\xF0\x04\xF3\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\x06\x51\x3F\xFF\xFF\xFD\x2C\xF7\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x40\x1F\xFF\xFF\xFF\xFD\xE7\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x40\x1F\xFF\xFF\xFF\xFE\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x09\x7F\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x3F\xFF'
|
||||||
|
b'\xFF\xFF\xF3\xBF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x22'
|
||||||
|
b'\x1F\xFF\xFF\xFF\xE3\x8F\x8F\xCF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFC\x80\x4F\xFF\xFF\xFF\xE3\x87\x8F\x8F\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xC4\x1F\xFF\xFF\xFF\xC3\x87\xC3\x1F\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFC\x51\x1F\xFF\xFF\xFF\xD7\x85\xC2\x3F\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x44\x3E\x3F\xFF\xFF\xC7\x94\x48\x7F'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x20\x0F\xFF\xFF\x81\x80'
|
||||||
|
b'\x40\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFD\x10\x40\x87\xFF\xFF'
|
||||||
|
b'\x19\x84\x44\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x01\x04\x0F'
|
||||||
|
b'\xFF\xFF\x00\x94\x4C\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x44'
|
||||||
|
b'\x03\xFF\xFF\xFE\x40\x0F\xCC\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFC\x00\x2F\xFF\xFF\xFE\x04\x3F\x98\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xC0\x10\xFF\xFF\xFF\xFD\x90\xB1\xD9\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xF0\x00\x87\xC1\xFF\xFF\xFF\x00\x63\x79\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xF0\x02\x3E\x00\x3F\xFF\xFE\x21\x87\x19\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF2\x25\xF0\x08\x1F\xFF\xFE\x77\x0F'
|
||||||
|
b'\xD1\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x3F\x80\x20\x1F\xFF\xFC'
|
||||||
|
b'\x78\x2F\xC3\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFD\xCC\x02\x7D\x1F'
|
||||||
|
b'\xFF\xFC\x70\x33\xE3\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x15'
|
||||||
|
b'\x3E\x1F\xFF\xF9\x39\x43\xE3\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xC4\x98\x3C\x5F\xFF\xF3\x1E\x03\xE3\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xC1\x80\x3E\x1F\xFF\xE7\x3E\x4B\xC1\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xD3\x82\x3C\x1F\xFF\xEF\x3E\x23\xC8\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xE3\xD8\x7C\x1F\xFF\xDE\x1E\x47\x98\x7F'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE3\x80\xFC\x5F\xFF\xDE\x3E\x53'
|
||||||
|
b'\x39\x3F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE3\xC3\xF8\x1F\xFF\xFF'
|
||||||
|
b'\x1E\x44\x7C\x0F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE7\x87\xFA\x3F'
|
||||||
|
b'\xFF\xFE\x1E\x40\xFE\x03\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE7\xD0'
|
||||||
|
b'\xF8\x3F\xFF\xFF\x1E\x83\xFE\x23\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xF7\xC0\x28\xBF\xFF\xFE\x0C\xC9\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xE4\x0B\x00\x7F\xFF\xFE\x2E\x83\xFF\x80\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xE1\x0F\x82\x7F\xFF\xFE\x0D\xC7\xFF\xC8\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x3F\xC8\x7F\xFF\xFE\x4B\xCF\xFF\xF0'
|
||||||
|
b'\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\xFF\xE0\xFF\xFF\xFF\x1F\xFF'
|
||||||
|
b'\xFF\xFE\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF1\xFF\xFF\xFF'
|
||||||
|
b'\xBF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x3F'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFC\x3F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFD\xFD\x3F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xF8\x7C\x17\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFC\x1F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xF8\x3C\x00\xFF\xFF\xFF\xFF\xFE\x3F\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFC\x0F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x70\x40\xFF\xFF\xFF\xFF\xFE\x3F'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFC\x2F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xF5\xFF\xFF\xFF\xFF\xFF\xFF\xFA\x01\x09\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFE\x3F\xFF\xFF\xFF\xFF\xF8\x07\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFC\x7F\xFF\xFF\xF0\x7F\xFF\xFF\xFF\xFF\xFF\xE0\x10\xFF\xFF\xFF'
|
||||||
|
b'\xFF\x1F\xFF\x3F\xFF\xFF\xFF\xFF\xF0\x8F\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xF8\x3F\xFF\xFF\xF0\x3F\xFF\xFF\xFF\xFF\xFF\x01\x11\xFF'
|
||||||
|
b'\xFF\xFF\xFF\x0F\xFF\x3F\xFF\xFF\xFF\xFF\xE0\x3F\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xE1\x3F\xFF\xFF\xF9\x3F\xFF\xFF\xFF\xFF\xFC\x10'
|
||||||
|
b'\x71\xFF\xFF\xFF\xFF\x0F\xFE\x3E\x3F\xFF\xFF\xFF\xC2\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xC4\x7F\xFF\xFF\xFC\x3F\xFF\xFC\xFF\xFF'
|
||||||
|
b'\xFE\x0C\x73\xFF\xFF\xFF\xFF\x87\xFE\x78\x1F\xFF\xFF\xFF\x83\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x10\x7F\xFF\xFF\xFE\x3F\xFF\xF8'
|
||||||
|
b'\x3F\xFF\xFF\xFC\x63\xFF\xFF\xFF\xFF\x87\xFE\x20\x9F\xFF\xFF\xFF'
|
||||||
|
b'\x1F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xEC\x40\xFF\xFF\xFF\xFF\x7F'
|
||||||
|
b'\xEF\xF0\x3F\xFF\xFF\xFC\x67\xFF\xFF\xFF\xFF\x8F\xFE\x02\x1F\xFF'
|
||||||
|
b'\xFF\xFE\x3F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x01\xFF\xFF\xFF'
|
||||||
|
b'\xFF\x7F\x83\xF1\x7F\xFF\xFF\xFC\xE7\xFF\xFF\xFF\xFF\x0F\xFE\x88'
|
||||||
|
b'\x3F\xFF\xFF\xFE\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF1\x07\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\x03\xE1\x7F\xFF\xFF\xFC\x6F\xFF\xFF\xFF\xFF\xA7'
|
||||||
|
b'\xFC\x10\xFF\xFF\xFF\xE7\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\x93\xFF\xFF\xFF\xFF\xFC\x27\xC7\xFF\xFF\xFF\xFE\xCF\xFA\x3F\xFF'
|
||||||
|
b'\xFF\x8F\xF0\x51\xFF\xFF\xFF\xC3\xFF\xF9\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\x07\xFF\xFF\xFF\xFF\xF0\x07\x83\xFF\xFF\xFF\xFC\xFF\xC0'
|
||||||
|
b'\x3F\xFF\xFF\x87\xFE\x03\xFF\xFF\xFF\xF1\xF9\xE1\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\x0F\xFF\xFF\xFF\xFF\xF0\x0F\x03\xFF\xFF\xFF\xFE'
|
||||||
|
b'\xFE\x00\x1F\xFF\xFF\x8F\xFE\x47\xFF\xFF\xFF\xF8\xF1\xC5\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFC\x1E\x3F\xFF\xFF\xFF\xFF\xFD\x4E\x69\xFF\xFF'
|
||||||
|
b'\xFF\xFE\xF0\x3D\x3F\xFF\xFF\x0F\xFC\x07\xFF\xFF\xFF\xFC\xF8\x07'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xF9\x00\x3F\xFF\xFF\xFF\xF0\xFF\x1F\xE3'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xC1\x7C\x1F\xFF\xFF\x8F\xFE\x27\xFF\xFF\xFF\xF8'
|
||||||
|
b'\xF8\x17\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF2\x7F\xFF\xFF\xFF\xC0\x7F'
|
||||||
|
b'\x01\xE3\xFF\xFF\xFF\xFF\x05\xF8\x3F\xFF\xCF\x8C\xFC\x30\xFF\xFF'
|
||||||
|
b'\xFF\xFC\xFA\x4F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x0F\xFF\xFF\xFF'
|
||||||
|
b'\x80\xFE\x23\xE0\x3F\xFF\xFF\xF8\x0F\xF8\xBF\xFF\xCF\x81\xFD\x70'
|
||||||
|
b'\xFF\xFF\xFF\xF8\x70\x3F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x8F\xFF'
|
||||||
|
b'\xFF\xFE\x12\xFE\x03\xE0\x7F\xFF\xFF\xC0\x27\xF0\x7F\xFF\xE7\x13'
|
||||||
|
b'\xFC\x61\xFF\xFF\xFF\xF8\xC0\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF1'
|
||||||
|
b'\xC1\xFF\xFF\xFC\x00\xFD\x0B\xE4\xFF\xFF\xFE\x04\x8F\xF2\x7F\xFF'
|
||||||
|
b'\xE7\x87\xF8\xC5\xFF\xFF\xFF\xF0\x04\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xE3\xF0\xFF\xFF\xFC\x80\xFC\x20\xE1\xFF\xFF\xFE\x11\xC7\xE0'
|
||||||
|
b'\xFF\xFF\xC3\x0F\xF0\x83\xFF\xFF\xFF\xF4\x90\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xE3\xF8\x3F\xFF\xFE\x35\xFE\x08\xE7\xFF\xFF\xFF\x07'
|
||||||
|
b'\x87\xE0\xFF\xFF\xC0\x2F\xE2\x0F\xFF\xFF\xFF\xF0\x00\x7F\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xC7\x7C\x0F\xFF\xFF\xF0\xFF\x98\xE7\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xC9\xC9\xFF\xFF\xC8\x9F\xC0\x1F\xFF\xFF\xFF\xE0\x02\x7F'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x8F\x2E\x81\xFF\xFF\xF1\xFF\x9C\xE7'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xC8\x43\xFF\xFF\x80\x1F\xC4\x7F\xFF\xFF\xFF\xE2'
|
||||||
|
b'\x48\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x1E\x03\x00\x0F\xFF\xE0\x0F'
|
||||||
|
b'\x1C\xEE\x01\xFF\xFF\xFF\xC4\x03\xFF\xFF\xC2\x1F\xE0\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xE0\x38\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3E\x03\xC4\x07\xFF'
|
||||||
|
b'\xE0\x03\x3C\x40\x00\xFF\xFF\xFF\xC6\x13\xFF\xFF\xC9\x9F\xF0\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xF0\xF8\x43\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x7F\x27\xE0'
|
||||||
|
b'\x03\xFF\xE9\xF0\x30\x00\x91\xFF\xFF\xFF\xC7\x47\xFF\xFF\xC3\x1F'
|
||||||
|
b'\xF9\x3F\xFF\xFF\xFF\xF2\xF9\x60\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x78'
|
||||||
|
b'\x07\xF0\x91\xFF\xE1\xCC\x60\x15\x4F\xFF\xFF\xFF\x87\x8F\xFF\xFF'
|
||||||
|
b'\xEF\xBF\xF3\x1F\xFF\xFF\xFF\xFF\xF8\x78\x0F\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xF0\xE0\x1F\xFC\x03\xFF\xE1\x9C\x1B\xFF\xFF\xFF\xFF\xFF\xC7\xFF'
|
||||||
|
b'\xFF\xFF\xFF\x1F\xF3\x8F\xFF\xFF\xFF\xFF\xF0\x7E\x01\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xE3\xE1\x3F\xFF\xFF\xFF\xE5\x3C\x0F\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xC7\xFF\xFF\xFF\xFF\x9F\xF3\xC3\xFF\xFF\xFF\xFF\xF9\x7F\x90\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\x0B\xF0\x3F\xFF\xFF\xFF\xE2\x39\xA7\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xC7\xFF\xFF\xFF\xFF\x1F\xE7\xE3\xFF\xFF\xFF\xFF\xF0\x7F'
|
||||||
|
b'\x02\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\x9F\xFE\x7C\x07\xFF\xFF\xE0\x3B\xC1\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\x87\xFF\xFF\xFF\xFF\x9F\xE7\xE0\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xF0\x7F\x08\x3F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x60\x03\xFF\xFF\xC0\xF7'
|
||||||
|
b'\xE0\xFF\xFF\xFF\xFF\xFF\xC3\xFF\xFF\xFF\xFF\x9F\xCF\xF0\x3F\xFF'
|
||||||
|
b'\xFF\xE0\x24\x7F\xE0\x3F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x93\xFF\xFF'
|
||||||
|
b'\xC8\x6F\xF0\x3F\xFF\xFF\xFF\xFF\x97\xFF\xFF\xFF\xFF\x9F\xCF\xF8'
|
||||||
|
b'\x1F\xFF\xEF\xF4\x80\x7F\xF0\x3F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x12\x03'
|
||||||
|
b'\xFF\xFF\xC1\xDF\xFC\x06\xFF\xFF\xFF\xFF\xC3\xFF\xFF\xFF\xFF\x3F'
|
||||||
|
b'\x9F\xFC\x83\xFF\xE7\xFE\x01\x7F\xFC\xBF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8'
|
||||||
|
b'\x1C\x0F\xFF\xFF\xC1\xBF\xFE\x00\x3F\xFF\xFF\xFF\x83\xFF\xFF\xFF'
|
||||||
|
b'\xFF\x9F\x3F\xFE\x01\xFF\xE7\xFF\x88\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xE0\xFF\xFF\xFF\xFF\xE7\xFF\xFF\x48\x03\xFF\xFF\xFF\xC7\xFF'
|
||||||
|
b'\xFF\xFF\xFE\x1E\xFF\xFE\x08\x7F\xE7\xFF\xE0\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\x85\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x80\x7F\xFF\xFF'
|
||||||
|
b'\xC3\xFF\xFF\xFF\xFF\x1F\xFF\xFF\xA0\x7F\xC6\xFF\xF3\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\x57\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x05\xFF'
|
||||||
|
b'\xFF\xFF\xC7\xFF\xFF\xFF\xFF\x1F\xFF\xFF\xD0\x7F\xE0\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\x83\xFF\xFF\xFF\xFF\x1F\xFF\xFF\xFF\xFF\xC9\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xCB\xFF\xFF\xFF\xFF\x5F\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xC1\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC7\xFF\xFF\xFF\xFF\x9F\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xD3\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC7\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xE3\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC7\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xF7\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xEF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xEF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||||
|
b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF')
|
||||||
BIN
tool/test_iamge_white_bg.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
tool/test_iamge_white_bg2.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
46
tool/verify_fix.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
验证修复后的图片数据
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 导入旧的(有问题的)和新的(修复后的)图片数据
|
||||||
|
from test_iamge import test_iamge as old_data
|
||||||
|
from test_iamge_fixed import test_iamge_fixed as new_data
|
||||||
|
|
||||||
|
print("=== 验证图片数据修复 ===\n")
|
||||||
|
|
||||||
|
# 检查数据长度
|
||||||
|
print(f"旧数据长度: {len(old_data)} 字节")
|
||||||
|
print(f"新数据长度: {len(new_data)} 字节")
|
||||||
|
print(f"预期长度: {(400 * 300) // 8} 字节\n")
|
||||||
|
|
||||||
|
# 检查旧数据中的换行符
|
||||||
|
newline_count = old_data.count(0x0A) # 0x0A 是换行符 \n
|
||||||
|
print(f"旧数据中的换行符(0x0A)数量: {newline_count}")
|
||||||
|
print(f"新数据中的换行符(0x0A)数量: {new_data.count(0x0A)}\n")
|
||||||
|
|
||||||
|
# 显示前几个字节
|
||||||
|
print("旧数据前32字节:")
|
||||||
|
print(' '.join(f'{b:02X}' for b in old_data[:32]))
|
||||||
|
print("\n新数据前32字节:")
|
||||||
|
print(' '.join(f'{b:02X}' for b in new_data[:32]))
|
||||||
|
|
||||||
|
# 检查数据类型
|
||||||
|
print(f"\n旧数据类型: {type(old_data)}")
|
||||||
|
print(f"新数据类型: {type(new_data)}")
|
||||||
|
|
||||||
|
print("\n=== 结论 ===")
|
||||||
|
if newline_count > 0:
|
||||||
|
print(f"❌ 旧数据包含 {newline_count} 个错误的换行符,会导致显示乱码")
|
||||||
|
else:
|
||||||
|
print("✓ 旧数据没有换行符问题")
|
||||||
|
|
||||||
|
if new_data.count(0x0A) == 0 or new_data.count(0x0A) < newline_count:
|
||||||
|
print("✓ 新数据已修复,不包含错误的换行符")
|
||||||
|
else:
|
||||||
|
print("❌ 新数据仍有问题")
|
||||||
|
|
||||||
|
if len(new_data) == 15000:
|
||||||
|
print("✓ 新数据长度正确 (400x300/8 = 15000 字节)")
|
||||||
|
else:
|
||||||
|
print(f"❌ 新数据长度不正确,应该是 15000 字节")
|
||||||