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
|
||||
13
.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_BROKER_HOST=localhost
|
||||
MQTT_BROKER_HOST=luna-mqtt
|
||||
MQTT_BROKER_PORT=1883
|
||||
# MQTT_USERNAME=
|
||||
# MQTT_PASSWORD=
|
||||
MQTT_USERNAME=luna2025
|
||||
MQTT_PASSWORD=123luna2021
|
||||
|
||||
# 应用配置
|
||||
APP_NAME=墨水屏桌面屏幕系统
|
||||
@@ -26,3 +27,7 @@ INK_HEIGHT=300
|
||||
SECRET_KEY=123tangledup-ai
|
||||
ALGORITHM=HS256
|
||||
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
|
||||
16
.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_BROKER_HOST=localhost
|
||||
MQTT_BROKER_HOST=luna-mqtt
|
||||
MQTT_BROKER_PORT=1883
|
||||
# MQTT_USERNAME=
|
||||
# MQTT_PASSWORD=
|
||||
MQTT_USERNAME=luna2025
|
||||
MQTT_PASSWORD=123luna2021
|
||||
|
||||
# 应用配置
|
||||
APP_NAME=墨水屏桌面屏幕系统
|
||||
@@ -23,6 +23,10 @@ INK_WIDTH=400
|
||||
INK_HEIGHT=300
|
||||
|
||||
# 安全配置
|
||||
SECRET_KEY=your-secret-key-change-in-production
|
||||
SECRET_KEY=123tangledup-ai
|
||||
ALGORITHM=HS256
|
||||
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
347
admin_routes.py
@@ -6,10 +6,12 @@ from typing import Optional, List
|
||||
import json
|
||||
import os
|
||||
import secrets
|
||||
from datetime import datetime
|
||||
from config import settings
|
||||
|
||||
from database import get_db
|
||||
from models import Device as DeviceModel, Content as ContentModel
|
||||
from schemas import DeviceCreate, ContentCreate
|
||||
from models import Device as DeviceModel, Content as ContentModel, Todo as TodoModel
|
||||
from schemas import DeviceCreate, ContentCreate, TodoCreate, TodoUpdate
|
||||
from image_processor import image_processor
|
||||
from mqtt_manager import mqtt_manager
|
||||
|
||||
@@ -19,6 +21,68 @@ templates = Jinja2Templates(directory="templates")
|
||||
# 创建管理后台路由
|
||||
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)
|
||||
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()
|
||||
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_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", {
|
||||
"request": request,
|
||||
"device_count": device_count,
|
||||
"active_device_count": active_device_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_contents": recent_contents
|
||||
"recent_todos": recent_todos
|
||||
})
|
||||
|
||||
@admin_router.get("/devices", response_class=HTMLResponse)
|
||||
@@ -342,3 +417,261 @@ async def upload_image(request: Request, db: Session = Depends(get_db)):
|
||||
"devices": devices,
|
||||
"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 api import devices, contents
|
||||
from api import devices, contents, todos
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
# 注册所有路由
|
||||
api_router.include_router(devices.router)
|
||||
api_router.include_router(contents.router)
|
||||
# 注册所有路由,并添加全局安全要求
|
||||
api_router.include_router(devices.router, prefix="/devices")
|
||||
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 import func
|
||||
from typing import List, Optional
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import importlib.util
|
||||
|
||||
from database import get_db
|
||||
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 image_processor import image_processor
|
||||
from config import settings
|
||||
from auth import get_api_key
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api",
|
||||
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(
|
||||
device_id: str,
|
||||
content: ContentCreate,
|
||||
@@ -60,7 +63,7 @@ async def create_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(
|
||||
device_id: str,
|
||||
skip: int = 0,
|
||||
@@ -87,7 +90,7 @@ async def list_content(
|
||||
contents = query.order_by(ContentModel.version.desc()).offset(skip).limit(limit).all()
|
||||
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(
|
||||
device_id: str,
|
||||
version: int,
|
||||
@@ -141,7 +144,7 @@ async def get_content(
|
||||
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(
|
||||
device_id: str,
|
||||
version: int,
|
||||
@@ -176,7 +179,7 @@ async def update_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(
|
||||
device_id: str,
|
||||
version: int,
|
||||
@@ -250,7 +253,7 @@ async def get_latest_content(device_id: str, db: Session = Depends(get_db)):
|
||||
created_at=content.created_at
|
||||
)
|
||||
|
||||
@router.post("/upload")
|
||||
@router.post("/upload", dependencies=[Depends(get_api_key)])
|
||||
async def upload_image(
|
||||
device_id: str = Query(..., description="设备ID"),
|
||||
version: Optional[int] = Query(None, description="内容版本,如果提供则更新指定版本"),
|
||||
@@ -337,3 +340,332 @@ async def upload_image(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
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 typing import List, Optional
|
||||
from datetime import datetime
|
||||
@@ -9,13 +9,13 @@ from schemas import Device as DeviceSchema, DeviceCreate, DeviceUpdate, Bootstra
|
||||
from models import Device as DeviceModel
|
||||
from database import Content as ContentModel
|
||||
from mqtt_manager import mqtt_manager
|
||||
from auth import get_api_key
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/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)):
|
||||
"""
|
||||
注册新设备
|
||||
@@ -46,7 +46,7 @@ async def create_device(device: DeviceCreate, db: Session = Depends(get_db)):
|
||||
|
||||
return db_device
|
||||
|
||||
@router.get("/", response_model=List[DeviceSchema])
|
||||
@router.get("/", response_model=List[DeviceSchema], dependencies=[Depends(get_api_key)])
|
||||
async def list_devices(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
@@ -68,7 +68,7 @@ async def list_devices(
|
||||
devices = query.offset(skip).limit(limit).all()
|
||||
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)):
|
||||
"""
|
||||
获取设备详情
|
||||
@@ -81,7 +81,7 @@ async def get_device(device_id: str, db: Session = Depends(get_db)):
|
||||
)
|
||||
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(
|
||||
device_id: str,
|
||||
device_update: DeviceUpdate,
|
||||
@@ -108,7 +108,7 @@ async def update_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)):
|
||||
"""
|
||||
删除设备
|
||||
@@ -126,11 +126,12 @@ async def delete_device(device_id: str, db: Session = Depends(get_db)):
|
||||
db.delete(device)
|
||||
db.commit()
|
||||
|
||||
@router.get("/{device_id}/bootstrap", response_model=BootstrapResponse)
|
||||
async def device_bootstrap(device_id: str, db: Session = Depends(get_db)):
|
||||
@router.post("/{device_id}/bootstrap", response_model=BootstrapResponse, dependencies=[Depends(get_api_key)])
|
||||
async def bootstrap_device(device_id: str, db: Session = Depends(get_db)):
|
||||
"""
|
||||
设备启动获取当前版本信息
|
||||
设备引导 - 获取设备配置和内容
|
||||
"""
|
||||
# 验证设备是否存在
|
||||
device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first()
|
||||
if not device:
|
||||
raise HTTPException(
|
||||
@@ -138,29 +139,19 @@ async def device_bootstrap(device_id: str, db: Session = Depends(get_db)):
|
||||
detail="设备不存在"
|
||||
)
|
||||
|
||||
# 更新设备最后在线时间
|
||||
device.last_online = datetime.utcnow()
|
||||
db.commit()
|
||||
|
||||
# 获取最新的活跃内容
|
||||
latest_content = db.query(ContentModel).filter(
|
||||
ContentModel.device_id == device_id,
|
||||
ContentModel.is_active == True
|
||||
).order_by(ContentModel.version.desc()).first()
|
||||
# 获取设备场景的内容
|
||||
contents = db.query(ContentModel).filter(ContentModel.scene == device.scene).all()
|
||||
|
||||
# 构建响应
|
||||
response = BootstrapResponse(
|
||||
device_id=device_id,
|
||||
timezone=latest_content.timezone if latest_content else "Asia/Shanghai",
|
||||
time_format=latest_content.time_format if latest_content else "%Y-%m-%d %H:%M"
|
||||
scene=device.scene,
|
||||
contents=contents
|
||||
)
|
||||
|
||||
if latest_content:
|
||||
response.content_version = latest_content.version
|
||||
response.last_updated = latest_content.created_at
|
||||
|
||||
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)):
|
||||
"""
|
||||
获取设备状态
|
||||
|
||||
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
|
||||
|
||||
class Settings(BaseSettings):
|
||||
# 数据库配置
|
||||
database_url: str = "postgresql://luna:123luna@121.43.104.161:6432/luna_ink"
|
||||
# 数据库配置1
|
||||
#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_broker_host: str = "localhost"
|
||||
@@ -25,11 +27,16 @@ class Settings(BaseSettings):
|
||||
ink_height: int = 300
|
||||
|
||||
# 安全配置
|
||||
secret_key: str = "your-secret-key-change-in-production"
|
||||
secret_key: str = "123tangledup-ai"
|
||||
algorithm: str = "HS256"
|
||||
access_token_expire_minutes: int = 30
|
||||
|
||||
# 管理员配置
|
||||
admin_username: str = "admin"
|
||||
admin_password: str = "123456"
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
case_sensitive = False
|
||||
|
||||
settings = Settings()
|
||||
16
database.py
@@ -40,6 +40,22 @@ class Content(Base):
|
||||
# 关联设备
|
||||
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)
|
||||
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.staticfiles import StaticFiles
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from starlette.middleware.sessions import SessionMiddleware
|
||||
from contextlib import asynccontextmanager
|
||||
import logging
|
||||
import os
|
||||
@@ -10,6 +11,7 @@ from database import init_db
|
||||
from mqtt_manager import mqtt_manager
|
||||
from api import api_router
|
||||
from admin_routes import admin_router
|
||||
from auth import APIKeyMiddleware, AdminAuthMiddleware
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
@@ -51,10 +53,21 @@ async def lifespan(app: FastAPI):
|
||||
|
||||
# 创建FastAPI应用
|
||||
app = FastAPI(
|
||||
title=settings.app_name,
|
||||
description="基于 FastAPI + MQTT + HTTP/HTTPS + NTP 的轻量级墨水屏显示系统服务端",
|
||||
title="墨水屏桌面屏幕系统 API",
|
||||
description="用于管理墨水屏设备、内容和待办事项的API",
|
||||
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中间件
|
||||
@@ -66,11 +79,20 @@ app.add_middleware(
|
||||
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")
|
||||
|
||||
# 注册API路由
|
||||
app.include_router(api_router)
|
||||
app.include_router(api_router, prefix="/api")
|
||||
|
||||
# 包含管理后台路由
|
||||
app.include_router(admin_router, prefix="/admin", tags=["管理后台"])
|
||||
|
||||
@@ -2,6 +2,6 @@ from sqlalchemy.orm import Session
|
||||
from database import Base
|
||||
|
||||
# 导入所有模型以确保它们被注册到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
|
||||
@@ -166,5 +166,42 @@ class MQTTManager:
|
||||
except Exception as 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_manager = MQTTManager()
|
||||
@@ -11,3 +11,5 @@ python-jose[cryptography]==3.3.0
|
||||
passlib[bcrypt]==1.7.4
|
||||
aiofiles==23.2.1
|
||||
httpx==0.25.2
|
||||
itsdangerous==2.1.2
|
||||
jinja2==3.1.2
|
||||
26
schemas.py
@@ -87,3 +87,29 @@ class MQTTStatus(BaseModel):
|
||||
timestamp: int = Field(..., description="时间戳")
|
||||
device_id: str = Field(..., description="设备ID")
|
||||
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>内容管理
|
||||
</a>
|
||||
</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">
|
||||
<a class="nav-link {% if '/admin/upload' in request.url.path %}active{% endif %}" href="/admin/upload">
|
||||
<i class="fas fa-upload me-1"></i>图片上传
|
||||
@@ -45,7 +50,22 @@
|
||||
<button class="btn btn-outline-light me-2" id="themeToggle" title="切换主题">
|
||||
<i class="fas fa-palette"></i>
|
||||
</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>
|
||||
<span id="currentTime"></span>
|
||||
</span>
|
||||
|
||||
@@ -100,20 +100,45 @@
|
||||
<div class="row">
|
||||
<div class="col-5">
|
||||
<div class="icon-big text-center">
|
||||
<i class="fas fa-play-circle"></i>
|
||||
<i class="fas fa-tasks"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<div class="numbers">
|
||||
<p class="card-category">活跃内容</p>
|
||||
<h3 class="card-title">{{ active_content_count }}</h3>
|
||||
<p class="card-category">待办事项</p>
|
||||
<h3 class="card-title">{{ todo_count }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<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>
|
||||
@@ -172,36 +197,44 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 最近创建的内容 -->
|
||||
<!-- 最近待办事项 -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card shadow-sm">
|
||||
<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">
|
||||
<i class="fas fa-clock me-2"></i>最近创建的内容
|
||||
<i class="fas fa-tasks me-2"></i>最近待办事项
|
||||
</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>查看全部
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if recent_contents %}
|
||||
{% if recent_todos %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead class="table-light">
|
||||
<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-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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for content in recent_contents %}
|
||||
{% for item in recent_todos %}
|
||||
{% set todo = item.todo %}
|
||||
{% set device = item.device %}
|
||||
<tr>
|
||||
<td><a href="/admin/devices/{{ content.device_id }}" class="text-decoration-none"><code>{{ content.device_id }}</code></a></td>
|
||||
<td><a href="/admin/devices/{{ content.device_id }}/contents/{{ content.version }}" class="text-decoration-none">{{ content.title }}</a></td>
|
||||
<td><span class="badge bg-info">v{{ content.version }}</span></td>
|
||||
<td>{{ content.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
<td><a href="/admin/todos/{{ todo.id }}" class="text-decoration-none">{{ todo.title }}</a></td>
|
||||
<td><a href="/admin/devices/{{ device.device_id }}" class="text-decoration-none">{{ device.name or device.device_id }}</a></td>
|
||||
<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>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@@ -209,8 +242,8 @@
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<i class="fas fa-inbox fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">暂无内容</p>
|
||||
<i class="fas fa-clipboard-list fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">暂无待办事项</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -240,13 +273,13 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="/admin/upload" class="btn btn-success btn-lg btn-block">
|
||||
<i class="fas fa-upload me-2"></i> 上传图片
|
||||
<a href="/admin/todos/add" class="btn btn-warning btn-lg btn-block">
|
||||
<i class="fas fa-plus me-2"></i> 添加待办
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="/admin/devices" class="btn btn-warning btn-lg btn-block">
|
||||
<i class="fas fa-tv me-2"></i> 设备管理
|
||||
<a href="/admin/todos" class="btn btn-success btn-lg btn-block">
|
||||
<i class="fas fa-tasks me-2"></i> 待办管理
|
||||
</a>
|
||||
</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 字节")
|
||||