文章目录
0、来由
- 去官网查课表太慢太麻烦
- 在手机上留截图看屏幕小(主要那段时间看电脑多)
- 电脑上存截图也得找
- 那时候还没发现小爱自带的课表(也可能自动导入功能还没有)
于是,就有了这个想法,之前也实现过(P好每周的课表蒙版图,存起来,然后写个脚本把课表蒙版跟壁纸合并,然后调用接口更换壁纸)文章考古-点这里。但是P图毕竟费时间,想要修改也比较费劲,也就自己折腾自己。
后来就有了新想法:
- 根据课表数据,
- 自动绘制课表蒙版,
- 自动贴合背景,
- 自动换壁纸。
1、设计、实现思路
1-1 课表数据文档设计
将学期课表按一定规则,存储为文本文件,然后程序读取文件,根据当前时间与设定好的学期开始时间计算出当前周数,并设置学期总周数,超过则不显示课表。
当时编程还不是很规范,许多配置参数都只写死到代码中,要修改都只能到代码中找,很不方便。比如:
- 学期开始时间
- 总周数
- 壁纸文件夹路路径
- 课表文件路径
- 课表位置偏移量
- 。。。
其实,可将这些参数,同课表数据一样,抽离出来作为配置项,通过配置文件获取,从而就大大方便了定制化。
学期课表中,需要存储一些必要的信息项:
- 课程名称。为了方便显示,尽量简写
- 课程教室。
- 课程时间。也可以记录为:当天的第几节课
- 课程跨度。课程的开课周数,如
[1, 3-6, 10]
表示1周,3-6周,10周有课。
根据上述这些信息,就可以完整提取出每一周的课表。根据一些实际情况,做出一些调整:将教室
、课程简写
合并为课程名称;于是,设置一个wks
的列表表示周数,如wks[1:5]+wks[8:9]
表示: 1-4周+第8周
。
鉴于Python有个强悍的函数eval()
,那就直接把学期课表按照Python列表的格式设计,读取的时候直接对接就行;学期课表设计为一个含有五个元素的文档,每个元素为存储周一到周五中一天的课程信息;每个元素由六个列表单元构成,表示一天中的六节课(上午、下午、晚上各两节);而每个最小单元,也就是每节课,是一个二元列表(或是元组),第一个元素是设计后的 课表名称
如:B211大物
,第二个元素为课程跨度
,如:wks[1:5]+wks[8:9]
。
# 学期课表示例
[ # 周一课程
[('105射频', wks[1:6]+wks[7:12])],
[('419微测', wks[4:6]+wks[7:13])],
[],
[('407微波', wks[3:6]+wks[7:19])],
[('317信完', wks[1:6]+wks[7:14])],
[]
];
[ # 周二课程
[('305微原', wks[1:6]+wks[7:11]+wks[12:15])],
[('114数电', wks[1:4])],
[],
[('418随机', wks[1:6]+wks[7:11]+wks[12:13])],
[('114智能', wks[1:6]+wks[7:11]+wks[12:15])],
[]
];
[ # 周三课程
[('105射频', wks[1:6]+wks[7:12])],
[('211专业', wks[8:10])],
[],
[('211形政', wks[15:17])],
[],
[]
];
[ # 周四课程
[('305微原', wks[1:5]+wks[7:15]+wks[12:15])],
[('219编程', wks[1:6]+wks[7:10]), ('105工概', wks[10:18])],
[],
[('418随机', wks[1:5]+wks[7:13])],
[],
[]
];
[ # 周五课程
[('407微波', wks[3:6]+wks[7:17])],
[],
[],
[('518学导', wks[7:8]+wks[15:16])],
[('onl学导', wks[1:2]+wks[11:12])],
[],
];
1-2 解析获取周课表
根据学期课表的设计,便可以逆向解析到周课表。大致思路为:获取到学期课表列表后,循环遍历五天的课表信息,再遍历每天的课程信息,如果当前周数存在于当前课程跨度中,那么向周课表列表中加入该课程,否则置入空字符串表示没课。最终得到周课表。
- 重要信息:
- 当前周数
- 学期课表
# 获取此周课表
def getnow_cs(now_wk, cs_path):
'''
# now_wk: 当前周数
# cs_path: 课表文件地址
'''
# 临时周课表路径
path = op.join(op.split(cs_path)[0], 'tem_cls')
# 如果已经生成过本周课表txt 那么读取后直接返回,否则执行后续
if op.exists(c_path := op.join(path, f'{now_wk}周课表.txt')):
f = open(c_path, 'r', encoding='utf8')
if len(f.read()) > 20:
with open(c_path, 'r', encoding='utf8') as f:
now_cs = list(map(eval, f.read().split('\n')[:-1]))
f.close()
return now_cs
clss = [op.join(path, name) for name in os.listdir(path)]
# 删除过时的周课表
for k in clss:
os.remove(k)
now_cs = [[] for i in range(5)]
f = open(op.join(path, f'{now_wk}周课表.txt'), 'a', encoding='utf8')
for wk in range(5):
# cls 代表单天的课程列表
for cls in getall_cs(cs_path)[wk]:
flag = 1
# cl 代表每一节课
for cl in cls:
# 如果当前周在当前循环课程的 周跨度中
# 将该课程添加到改周课表对应位置
if now_wk in cl[1]:
now_cs[wk].append(cl[0])
flag = 0
# flag = 1 表示当节课没有课程安排
# 那么该位置添入 空字符串 占位
if flag:
now_cs[wk].append('')
f.write(str(now_cs[wk]) + '\n')
f.close()
return now_cs
# 获取学期课表 传入学期课表路径
def getall_cs(cs_path):
# 周数列表,最多应该不会超过 23 周
wks = [i for i in range(23)]
with open(cs_path, 'r', encoding='utf8') as f:
# eval 将每天课表的字符串数据转换为 列表;
# split 规则参照 1-1 的课表文档
lh = list(map(eval, f.read().split(';\n')[:-1]))
f.close()
return lh
1-3 获取当前周数
先睡了,下次更新…
# 获取周数
# 传入第一个周 周一的日期
# 返回:当前周数
def get_imgid(start_time='2020 8 31'):
#start_time = '2020 8 31'
start = time.strptime(start_time, '%Y %m %d')
start_days = int(time.strftime('%j', start))
now_days = int(time.strftime('%j', time.localtime()))
img_id = 1 + (now_days - start_days)//7
return img_id
1-4 绘制周课表蒙版、合并背景壁纸
生成课表蒙版+合并背景壁纸
# 制作、存储壁纸
# 传入本周课表、图片路径、输出路径
# 最终函数保存一课表图片文件
def get_wap(now_cs, img_path, out_path):
now_wk = op.split(out_path)[-1].split('周')[0][1:]
path = img_path
days = ['周一', '周二', '周三', '周四', '周五']
times = ['1-2', '3-4', '午休', '5-6', '7-8', '晚']
color = 'black' # 课颜色
tim_color = 'crimson' # 节数颜色
week_color = 'blueviolet' # 周N颜色
wek_color = 'azure' # N周颜色
color0 = 'lightgrey' # 矩形填充颜色
ret_color = None # 矩形图层颜色
alpha = .6
line_color = 'darkorange' # 竖线颜色
xline_color = 'turquoise' # 横线颜色
width = 2 # 线宽
font_size = 40 # 字体大小
font = ImageFont.truetype(r'c:\windows\fonts\simkai.TTF', font_size)
font1 = ImageFont.truetype(r'c:\windows\fonts\simkai.ttf', font_size+2) # 字体背景
color1 = 'grey' # 字体背景颜色
wx_font = ImageFont.truetype(r'c:\windows\fonts\simsun.ttc', 31) # 午休
# 横竖排间隔
dy = (font_size*1.2)//1
dx = (font_size*3.78)//1
ddy = 35 # 控制整体上下移动
dyy = 6 # 控制标题组上下移动
# 打开图片--转为RGBA模式
im = Image.open(path).convert('RGB')
# 圆角矩形底色
newi = Image.new('RGB', (1920, 1080))
dd = ImageDraw.Draw(newi)
x0, y0 = 4, 1080-(4*font_size+4*dy)-24 + ddy # 起始坐标 !!-1
r = 34 # 圆角半径
w, h = 817, 1080-y0 # 矩形框尺寸
'''Rounds''' # 四个角先画4个圆
dd.ellipse((x0, y0, x0 + r, y0 + r), fill=color0)
dd.ellipse((x0 + w - r, y0, x0 + w, y0 + r), fill=color0)
dd.ellipse((x0, y0 + h - r, x0 + r, y0 + h), fill=color0)
dd.ellipse((x0 + w - r, y0 + h - r, x0 + w, y0 + h), fill=color0)
'''rec.s''' # 两个方框相切
dd.rectangle((x0 + r / 2, y0, x0 + w - (r / 2), y0 + h), fill=color0)
dd.rectangle((x0, y0 + r / 2, x0 + w, y0 + h - (r / 2)), fill=color0)
# 遮罩, alpha 透明度调节
im1 = Image.blend(newi, im, alpha=alpha)
im.close()
newi.close()
# 获得带有底矩形框的背景 draw/im
draw = ImageDraw.Draw(im1)
# '第几周' 字样绘制
draw.text((x0+w//2.5, y0+8-dyy), f'第{now_wk}周', font=font, fill=wek_color)
draw.line((x0, y0+dy-dyy, w+x0, y0+dy-dyy), fill='gold', width=width+1)
# 课程 时间点 绘制 节数
xx, yy = x0+3, 1080-(4*font_size+3*dy)+25 + ddy # 节数起始坐标 !!-2
draw.line((xx + font_size*1.82//1-4, yy-4, w+x0, yy-4), fill=xline_color, width=width) # 分割线 周下边
for tim in times:
if tim == '午休':
# 先获取午休两线的坐标参数 --先不画
wx_y1, wx_y2 = yy+6, yy+dy-22
draw.text((x0, yy+2), tim, font=wx_font, fill='lawngreen')
else:
if tim=='晚':
draw.text((xx+10, yy-12), tim, font=font, fill=tim_color)
else:
draw.text((xx, yy), tim, font=font, fill=tim_color)
if tim != '3-4' and tim != '晚':
# 横线
draw.line((xx + font_size*1.82//1-4, yy+dy-8, w+x0, yy+dy-8), fill=xline_color, width=2)
if times.index(tim) == 2 or times.index(tim) == 1:
yy += dy-15
else:
yy += dy
# 课表内容绘制+周N
x = xx + font_size*1.82//1 # 周+课 起始坐标 !!-3
day = 0
for i_cls in now_cs:
y = 1080-(4*font_size+4*dy)+25 + ddy # 周+课 起始坐标 !!-3
draw.text((x + 30, y), days[day], font=font1, fill=color1) # 星期
draw.text((x+30, y), days[day], font=font, fill=week_color) # 星期
day += 1
# 竖线
draw.line((x-.045*dx//1, y+30, x-.045*dx//1, y+h-64), fill=line_color, width=width)
cnt = 0
for cl in i_cls:
if cnt == 2 or cnt == 3:
y += dy - 15
elif cnt == 5:
y += dy - 10
else:
y += dy
if cl:
draw.text((x-2, y-1), cl, font=font1, fill=color1)
draw.text((x, y), cl, font=font, fill=color) # 周n遍历每一节课
cnt += 1
x += dx
# 午休两根线
draw.line((xx + font_size*1.82//1-4, wx_y1, w+x0, wx_y1), fill='red', width=width)
draw.line((xx + font_size*1.82//1-4, wx_y2, w+x0, wx_y2), fill='red', width=width)
im1.save(out_path, quality=100)
1-5 调用接口更换壁纸
先睡了,下次更新…
# 换壁纸 具体 函数
def setWallpaper(image_path):
# 这是剽窃的,有时间研究一下啦
key = win32api.RegOpenKeyEx(win32con.HKEY_CURRENT_USER, "Control Panel\\Desktop", 0, win32con.KEY_SET_VALUE)
win32api.RegSetValueEx(key, "WallpaperStyle", 0, win32con.REG_SZ, "2")
win32api.RegSetValueEx(key, "TileWallpaper", 0, win32con.REG_SZ, "0")
win32gui.SystemParametersInfo(win32con.SPI_SETDESKWALLPAPER, image_path, 1 + 2)
# 换壁纸 框架
def setWallPaperBMP(imagePath):
bmpImage = Image.open(imagePath)
newPath = imagePath.replace('.jpg', '.bmp')
bmpImage.save(newPath, "BMP")
setWallpaper(newPath)
def ch_wallpaper(img_id, all_wek=18):
test = 1
if test:
root_path = r'c:\wallp\Schedule'
else:
root_path = os.getcwd()
bgs_path = op.join(root_path, 'bg') # 壁纸路径
cs_path = op.join(root_path, 'cls.txt') # 总课表路径
to_path = op.join(root_path, 'tem') # 课表背景路径
flag = op.join(root_path, r'flag\nshow')
bg_list = [op.join(bgs_path, name) for name in os.listdir(bgs_path)]
bg_path = random.choice(bg_list)
while not bg_path.endswith('.jpg'):
bg_path = random.choice(bg_list)
# 每次运行都要删除一遍文件 -- 课表图片
if len(lst := os.listdir(to_path)):
tems = [op.join(to_path, name) for name in lst]
for tem in tems:
os.remove(tem)
# 这里想办法搞一搞 周数大于18 或者 不想显示课表(判断某个文件在不在)
if (img_id > all_wek) or (op.exists(flag)): # 超过总周数就不贴课表
setWallpaper(bg_path)
else:
if img_id <= 0:
img_id = 1
img_name = f'第{img_id}周.png'
out_img = op.join(to_path, img_name)
get_wap(now_cs=getnow_cs(img_id, cs_path), img_path=bg_path, out_path=out_img)
if not op.exists(out_img):
return None
setWallpaper(out_img)
1-6 设置定时任务
先运行试试:
if __name__ == '__main__':
start_time = '2022 4 30'
screen_size = (1920, 1080)
all_wek = 18
img_id = get_imgid(start_time)
ch_wallpaper(img_id, all_wek=all_wek)
sys.exit()
定时任务
那么问题来了,代码是写好了,说好的自动换壁纸呢??!代码写好了就会自己
动运行? 不急,还有最后的操作,跟我一步一步来
- 首先在开始菜单搜索 任务计划程序,点击进入,如下图:
- 然后点击右上角创建基本任务
- 起个名称,下一步
- 选择 每天 ->下一步
- 这里设置一下,然后下一步
- 直接下一步
- 这里需要注意了
程序脚本|就只用填 python.exe
—|—
添加参数|填源代码的地址【例如:C:\users\lalala\desktop\ch_wallpaper.py ]
起始于|填的是你Python.exe的安装目录【例如:C:\Program Files\Python38\Python38】- 然后回到主界面,找到刚添加好的任务,点击属性,修改一下设置
最后,如果还是不能每天运行的话,可以参考我的暴保守做法,修改触发器,让它每次登录都运行一下
2、总结
2-1 用到的关键技术
- Python
eval()
:从文件获取周课表数据列表 - Python
time
库:时间处理 - Python
os
库:路径、文件处理 - Python
Pillow
库:图像处理 - Python
Windows接口
更换桌面壁纸
2-2 不规范、不足之处、改进方向
- 大部分参数与代码耦合度太高,调参是个大问题
- 配置文件功能简单,可以发挥更多作用:将更多参数配置迁移到配置文件中,代码打包后便可以只修改配置文件而定制程序。
- 配置文件按可以考虑采用
JSON
YAML
等 - 可以编写图形化界面导入学期课表 或者 使用爬虫到网站直接爬取学期课表。