AutoRing.py 发布版本

101 分钟阅读

#!/usr/bin/env python3
# AutoRing Project Main App autoring.pyw
# AutoRing项目主程序
# Version: stable-1-Non-LTS-BugFix-Final
# Author: lwd-temp
# https://github.com/lwd-temp/AutoRing.py
# 需要第三方库pygame(Sound) 可能需要pywin32 tkinter(GUI) 外部二进制程序cmdmp3 ffmpeg
# 部分功能为Windows设计,可能无法跨平台运行或需要修改。
import datetime
import json
import logging
import os
import random
import subprocess
import sys
import threading
import time

import pygame

# 初始化Pygame Mixer
pygame.mixer.init(buffer=10240)
# Bigger buffer to avoid strange sound effect when high I/O occures.

# 配置logging
# 设置日志文件名、记录等级和格式
LOG_FORMAT = "%(asctime)s - %(levelname)s - [%(origin)s] %(message)s"
logging.basicConfig(filename='autoring.log',
                    level=logging.DEBUG, format=LOG_FORMAT)


def infoLog(origin="UNKNOWN", msg="UNKNOWN"):
    # Info日志函数
    logging.info(str(msg), extra={'origin': str(origin)})
    print(str(origin)+":"+str(msg))  # 打印日志内容


def playSound(filename="begin.mp3"):
    # 播放音乐filename
    # 也可以选用pygame.mixer的其他函数
    file = str(filename)
    try:
        pygame.mixer.music.load(file)
        infoLog("playSound", "Play "+file)
        pygame.mixer.music.play()
    except:
        infoLog("playSound", "pygame.error!")  # 糟糕的错误处理,需要改进。
        altPlaySound(file)


def getSeconds(hour, minute, second=0):
    # 获取到某时刻秒
    nowtime = datetime.datetime.now()
    nowmon = nowtime.month
    nowday = nowtime.day
    nowye = nowtime.year
    strthe = str(nowye)+"-"+str(nowmon)+"-"+str(nowday) + \
        " "+str(hour)+":"+str(minute)+":"+str(second)+".0"
    thetime = datetime.datetime.strptime(strthe, "%Y-%m-%d %H:%M:%S.%f")
    delta = (thetime-nowtime).seconds
    return delta


def sleepTo(hour, minute, second=0):
    # 等待到某时 Sleep to sometime
    delay = 0
    #####################################################
    # Delay to sync with the stupid school timing system.
    delay = 12
    infoLog("sleepTo", "delay="+str(delay))
    #####################################################
    infoLog("sleepTo", str(hour)+" "+str(minute)+" "+str(second))
    delta = getSeconds(int(hour), int(minute), int(second))+delay
    infoLog("sleepTo", "Delta:"+str(delta)+" Sleeping...")
    time.sleep(delta)


def getPass(hour, minute, second=0):
    # Get if pass
    # return 1 when pass
    # 获取是否已过当前时间
    nowtime = datetime.datetime.now()
    ifpass = 0
    if int(hour) < nowtime.hour:
        ifpass = 1
    if int(hour) == nowtime.hour:
        if int(minute) < nowtime.minute:
            ifpass = 1
    if int(hour) == nowtime.hour:
        if int(minute) == nowtime.minute:
            if int(second) < nowtime.second:
                ifpass = 1
    return ifpass


def getWeekday(date=datetime.datetime.now()):
    # 获取星期1-7
    day = date.weekday()
    intday = int(day)+1
    ###################
    # Force Monday
    # intday = 1
    # End Force Monday
    ###################
    # Force Normal
    # intday = 2
    # End Force Normal
    ###################
    return intday


def getFilename(stat=6):
    # 根据事件获取文件名
    intstat = int(stat)
    fn = "alarm.mp3"  # Failsafe
    if intstat == 1:
        # normal上课
        # fn = "begin.mp3"
        fn = "nosound.mp3"
    if intstat == 2:
        # normal下课
        # fn = "over.mp3"
        fn = "nosound.mp3"
    if intstat == 3:
        # 放学 在此保留Failsafe
        # 需要注意放学事件的实际解决方案不同,见ringAt函数和randPlaySound函数。
        # fn = "afterschool.mp3"
        fn = "nosound.mp3"
    if intstat == 4:
        # special上课
        fn = "begin.mp3"
    if intstat == 5:
        # special下课
        fn = "over.mp3"
    if intstat == 6:
        # No sound 无声
        fn = "nosound.mp3"
    return fn


def randPlaySound():
    # Random Music Player
    # 1.mp3 2.mp3 3.mp3 4.mp3 etc.
    # 随机播放音频
    # 文件名1.mp3 2.mp3 ...... 17.mp3 ...... 30.mp3
    randnum = random.randint(1, 30)  # 生成文件名中的随机数部分
    #####################################
    # randnum = 17  # Force 17 强制“随机”
    #####################################
    randstr = str(randnum)
    fnstr = randstr+".mp3"
    infoLog("randPlaySound", fnstr)
    try:
        infoLog("json", "Try to read json.")  # 与autoconvert.py配合使用
        with open("music.json", "r") as data:
            filen = json.load(data)
        infoLog("json", str(filen))
        infoLog("chkfile", "Checking file.")
        with open(filen, "r") as testfile:  # 检测文件存在性
            infoLog("chkfile", "File exists.")
        playSound(filen)
        with open("music.json", "w") as data:  # 覆写json使其失效
            data.write("init")
            infoLog("json", "Rewrite json.")
    except:
        infoLog("json", "Failed.")  # 若出错
        playSound(fnstr)


def showMsgMain(msg):
    # Call showtext
    # Add pythonw to your $PATH
    # 调用showtext.pyw
    # pythonw需要被加入环境变量PATH
    infoLog("showMsgMain", str(msg))
    subprocess.call(["pythonw", "showtext.pyw", str(msg)])
    infoLog("showMsgMain", "exit")


def showMsg(msg):
    # Threading to avoid waiting.
    # 多线程用于避免等待
    infoLog("showMsg", str(msg))
    ddd = threading.Thread(target=showMsgMain, args=[str(msg)])
    ddd.start()


def altPlaySoundMain(file):
    # Call cmdmp3 to play sound.
    # In case of pygame.error
    # 调用cmdmp3.exe
    # 避免随机(?)出现的pygame.error影响播放
    infoLog("altPlaySoundMain", str(file))
    subprocess.call(["cmdmp3", str(file)])
    infoLog("altPlaySoundMain", "exit")


def altPlaySound(file):
    # Threading to avoid waiting.
    # 多线程
    infoLog("altPlaySound", str(file))
    ddd = threading.Thread(target=altPlaySoundMain, args=[str(file)])
    ddd.start()


def ringAt(hour, minute, second=0, stat=1, info="UNKNOWN"):
    # 响铃函数
    # stat 1 常规上课 2 常规下课 3 放学 4 特殊上课 5 特殊下课 6 无声
    # 调用举例ringAt(12,12,14,2,"Test") 12:12:14 常规下课 日志信息和屏幕显示"Test"
    # 可以用多线程和循环代替当前方案,但不具有必要性。
    infoLog("ringAt", str(hour)+":"+str(minute)+":" +
            str(second)+" "+str(stat)+" "+str(info))
    if getPass(int(hour), int(minute), int(second)) == 1:
        infoLog("ringAt", "Pass!")
    else:
        infoLog("ringAt", "Sleeping...")
        sleepTo(int(hour), int(minute), int(second))
        filename = getFilename(stat)
        stat = int(stat)
        if stat != 3:
            playSound(filename)
        if stat == 3:
            randPlaySound()
        showMsg(str(info))


def pyExecAt(hour, minute, second=0, code="print('No code.')"):
    # 在某时执行Python代码
    # 使用exec()
    # 调用举例pyExecAt(小时,分钟,秒,Python代码)
    infoLog("pyExecAt", str(hour)+":"+str(minute)+":" +
            str(second)+" "+str(code))
    if getPass(int(hour), int(minute), int(second)) == 1:
        infoLog("pyExecAt", "Pass!")
    else:
        infoLog("pyExecAt", "Sleeping...")
        sleepTo(int(hour), int(minute), int(second))
        infoLog("pyExecAt", "Exec")
        exec(str(code))


def shutitdown():
    # 关机操作
    infoLog("shutitdown", "Shutdown.")
    os.system("shutdown -s -t 600")  # 10分钟定时关机
    time.sleep(10)  # 等待执行和GUI提示
    os.system("rundll32.exe user32.dll,LockWorkStation")  # 锁定工作站
    time.sleep(540)  # 等待音乐播放完成退出程序 9分钟

#######################################################


def showTextScript(usermsg="No arg."):
    # 整合版本被设计用于紧急用途,但也可以在生产环境使用。
    # AutoRing Project GUI Not Stable
    # AutoRing项目 GUI部分 不稳定
    # Version: release
    # Author: lwd-temp
    # https://github.com/lwd-temp/AutoRing.py
    # 需要库pywin32(GUI) tkinter(GUI)
    # 这是个不理想的GUI,包含各种错误和可能的问题。
    # 使用shell调用
    # 显示第一个参数10s
    # import sys
    # import threading
    # import time
    import tkinter

    import pywintypes
    import win32api
    import win32con

    arg = sys.argv
    try:
        text = arg[2]  # It's [2] here.
    except:
        try:
            text = usermsg
        except:
            text = "Arg Error"

    infoLog("showTextScript", text)

    # print(text)

    def showmsg(textmsg):
        # 浮动文字
        # Source: https://stackoverflow.com/questions/21840133/how-to-display-text-on-the-screen-without-a-window-using-python
        label = tkinter.Label(text=textmsg, font=(
            'Times New Roman', '80'), fg='red', bg='white')
        label.master.overrideredirect(True)
        label.master.geometry("+0+300")
        label.master.lift()
        label.master.wm_attributes("-topmost", True)
        label.master.wm_attributes("-disabled", True)
        label.master.wm_attributes("-transparentcolor", "white")

        hWindow = pywintypes.HANDLE(int(label.master.frame(), 16))
        # http://msdn.microsoft.com/en-us/library/windows/desktop/ff700543(v=vs.85).aspx
        # The WS_EX_TRANSPARENT flag makes events (like mouse clicks) fall through the window.
        exStyle = win32con.WS_EX_COMPOSITED | win32con.WS_EX_LAYERED | win32con.WS_EX_NOACTIVATE | win32con.WS_EX_TOPMOST | win32con.WS_EX_TRANSPARENT
        win32api.SetWindowLong(hWindow, win32con.GWL_EXSTYLE, exStyle)

        def des():
            # 显示时间10s
            time.sleep(10)
            label.destroy()
            label.quit()

        ddd = threading.Thread(target=des)
        ddd.start()

        label.pack()
        label.mainloop()

    showmsg(text)
    sys.exit()
#######################################################
#######################################################


def autoConvertScript():
    # 整合版本被设计用于紧急用途,但也可以在生产环境使用。
    # AutoRing项目 自助式自动化放学铃设置 面向用户
    # import datetime
    # import json
    # import logging
    # import subprocess
    # import sys

    # LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
    # logging.basicConfig(filename='ac.log',
    #                     level=logging.DEBUG, format=LOG_FORMAT)

    nowtime = datetime.datetime.now()
    filename = str(nowtime.year)+"-"+str(nowtime.month)+"-"+str(nowtime.day) + \
        "-"+str(nowtime.hour)+"-"+str(nowtime.minute) + \
        "-"+str(nowtime.second)+".mp3"
    infoLog("autoConvertScript", "Run")

    # Call ffmpeg

    def ffmpeg(infi, outfi):
        infoLog("autoConvertScript", "ffmpeg "+str(infi)+" "+str(outfi))
        subprocess.call(["ffmpeg", "-i", str(infi), str(outfi)])
        infoLog("autoConvertScript", "ffmpeg exit")

    print("自助式自动化放学铃设置 Beta2版本")
    print("您的所有操作都将被记录备案,请不要恶意提交。")
    # print("不一定需要重命名媒体文件为123并将其复制到桌面")

    try:
        infoLog("autoConvertScript", "Try to read json.")
        with open("music.json", "r") as data:
            filen = json.load(data)
        infoLog("autoConvertScript", "Json exists.")
        with open(filen, "r") as testfile:
            infoLog("autoConvertScript", "File exists.")
        print("已存在人工设置")
        infoLog("autoConvertScript", "Operation exists.")
        oped = 1
    except:
        infoLog("autoConvertScript", "Operation chk OK.")
        print("当前无人工设置")
        oped = 0

    # To disable functions.
    #########################
    # oped = 1
    #########################

    if oped == 1:
        print("当日已存在人工设置,为确保不覆写设置已禁止操作。")
        print("按Enter退出程序")
        userinput = input()
        infoLog("autoConvertScript", str(userinput))
        if userinput != "debug":
            sys.exit()
        else:
            print("Input Debugger Key:")
            dkr = str(nowtime.year+1)+str(nowtime.month*2) + \
                str(nowtime.day+1)  # Debugger Key生成
            dk = input()
            infoLog("autoConvertScript", str(dk))
            if dk == dkr:
                infoLog("autoConvertScript", "Hello,debugger!")
                with open("music.json", "w") as data:
                    data.write("init")
                    infoLog("autoConvertScript", "Json rewrite done.")
                    print("Json已覆写")
            else:
                infoLog("autoConvertScript", "Wrong Debugger Key.")
                sys.exit()

    fp = input("输入媒体文件路径或直接将文件拖入窗口并按下Enter:")
    while fp.rstrip() == "":
        infoLog("autoConvertScript", "Empty file path.")
        fp = input("输入媒体文件路径或直接将文件拖入窗口并按下Enter:")

    infoLog("autoConvertScript", "用户输入:"+fp)

    print("用户输入:"+fp)

    # Fix cmd file dragging path complete feature
    if fp[0] == '"':
        if fp[-1] == '"':
            fp = fp[1:-1]

    infoLog("autoConvertScript", "文件路径:"+fp)

    print("文件路径:"+fp)

    comment = input("输入本次提交描述并按Enter:")
    infoLog("autoConvertScript", "Comment:"+comment)

    try:
        with open(fp, "r") as testfile:
            print("文件存在")
            infoLog("autoConvertScript", "File exists.")
    except:
        print("文件不存在")
        infoLog("autoConvertScript", "File failed.")
        sys.exit()

    print("调用ffmpeg,请注意错误信息...")
    ffmpeg(fp, filename)
    print("转码结束,请注意错误信息。")

    try:
        with open(filename, "r") as testfile:
            infoLog("autoConvertScript", "File ok.")
    except:
        infoLog("autoConvertScript", "File failed.")
        print("文件未生成,转码错误。")
        sys.exit()

    print("写入json")
    infoLog("autoConvertScript", "Try to write json.")
    with open("music.json", "w") as data:
        json.dump(filename, data)

    infoLog("autoConvertScript", "Done")
    print("完成,你可以安全地关闭窗口了。")
    infoLog("autoConvertScript", "Exit.")

    sys.exit()
#######################################################


def console():
    # Console Mode
    infoLog("console", "Welcome to the AR Console!")
    infoLog("console", "Hello from the AR Developer!")
    infoLog("HELP", "ringAt(hour,minute,second,stat,info)")
    infoLog("HELP", "stat:1-normBegin 2-normOver 3-AfterSchool 4-specBegin 5-specOver 6-NoSound")
    infoLog("HELP", "infoLog(origin,msg)")
    infoLog("HELP", "getSeconds(hour,minute,second)")
    infoLog("HELP", "playSound(filename)")
    infoLog("HELP", "pyExecAt(hour,minute,second,code)")
    infoLog("HELP", "showMsg(msg)")
    infoLog("HELP", "More functions available.Please read the source code.")
    infoLog("console", "Syntax: FUNCTION [ARG1] [ARG2] ...")
    infoLog("console", "Example: ringAt 12 33 23 3 hello")
    infoLog("console", "DANGEROUS!The AR Console is designed for developers.")
    infoLog("console", "Please read the full source code before using the console.")
    while True:
        userinput = input("ARConsole>>>")
        while userinput.rstrip() == "":
            infoLog("console", "Empty")
            userinput = input("ARConsole>>>")
        userinput = userinput.split()
        infoLog("console", str(userinput))
        if userinput[0] == "exit":
            infoLog("console", "exit")
            sys.exit()
        else:
            userfunc = userinput[0]
            infoLog("console", userfunc)
            try:
                userarg = userinput[1:]
                infoLog("console", str(userarg))
            except:
                userarg = []
                infoLog("console", "Arg Error, use empty arg")
        try:
            infoLog("console", "run")
            execthread = "threading.Thread(target=" + \
                str(userfunc)+", args="+str(userarg)+").start()"
            infoLog("console", execthread)
            exec(execthread)
        except:
            infoLog("console", "ERROR")


def zwt():
    # 此彩蛋即将结束支持
    # In memory of the one who made a difference to me.
    # import datetime
    date = datetime.datetime.today()
    if date.month == 4:
        if date.day >= 20:
            infoLog("zwt", "Happy birthday ZWT!")
    if date.month == 5:
        if date.day == 5:
            count = 0
            while count != 4:
                infoLog("zwt", "Happy birthday ZWT!")
                count = count+1
        if date.day <= 15:
            infoLog("zwt", "Happy birthday ZWT!")


def dailySchedule():
    # 标准时间表
    infoLog("dailySchedule", "Hello from the AR Developer!")
    if getWeekday() == 1:
        # 若周一
        infoLog("dailySchedule", "Today is Monday")
        ringAt(7, 40, 0, 1, "1:Class Begin")
        ringAt(8, 20, 0, 2, "1:Class Over")
        ringAt(8, 30, 0, 1, "2:Class Begin")
        ringAt(9, 10, 0, 2, "2:Class Over")
        ringAt(9, 20, 0, 1, "3:Class Begin")
        ringAt(10, 0, 0, 2, "3:Class Over")
        ringAt(10, 30, 0, 1, "4:Class Begin")
        ringAt(11, 10, 0, 2, "4:Class Over")
        ringAt(11, 20, 0, 1, "5:Class Begin")
        ringAt(12, 0, 0, 2, "5:Class Over")
        ringAt(13, 40, 0, 1, "6:Class Begin")
        ringAt(14, 20, 0, 2, "6:Class Over")
        ringAt(14, 30, 0, 1, "7:Class Begin")
        ringAt(15, 10, 0, 2, "7:Class Over")
        ringAt(15, 40, 0, 1, "8:Class Begin")
        ringAt(17, 0, 0, 2, "9:Class Over")
        ringAt(17, 40, 0, 1, "1st Self-study Begin")
        ringAt(18, 40, 0, 2, "1st Self-study Over")
        ringAt(18, 50, 0, 1, "2nd Self-study Begin")
        ringAt(19, 50, 0, 2, "2nd Self-study Over")
        ringAt(20, 0, 0, 4, "3rd Self-study Begin")
        ringAt(22, 0, 0, 3, "3rd Self-study Over")
        pyExecAt(22, 0, 30, "shutitdown()")
    elif getWeekday() == 7:
        # 若周日
        infoLog("dailySchedule", "Today is Sunday.")
        ringAt(12, 0, 0, 3, "School Over")
        pyExecAt(12, 0, 30, "shutitdown()")
    elif getWeekday() == 6:
        # 若周六
        infoLog("dailySchedule", "Today is Saturday.")
        ringAt(8, 0, 0, 1, "1:Class Begin")
        ringAt(8, 40, 0, 2, "1:Class Over")
        ringAt(8, 50, 0, 1, "2:Class Begin")
        ringAt(9, 30, 0, 2, "2:Class Over")
        ringAt(9, 40, 0, 1, "3:Class Begin")
        ringAt(10, 20, 0, 2, "3:Class Over")
        ringAt(10, 30, 0, 1, "4:Class Begin")
        ringAt(11, 10, 0, 2, "4:Class Over")
        ringAt(11, 20, 0, 1, "5:Class Begin")
        ringAt(12, 0, 0, 2, "5:Class Over")
        ringAt(13, 40, 0, 1, "6:Class Begin")
        ringAt(14, 20, 0, 2, "6:Class Over")
        ringAt(14, 30, 0, 1, "7:Class Begin")
        ringAt(15, 10, 0, 2, "7:Class Over")
        ringAt(15, 40, 0, 1, "8:Class Begin")
        ringAt(17, 0, 0, 2, "9:Class Over")
        ringAt(17, 40, 0, 1, "1st Self-study Begin")
        ringAt(18, 40, 0, 2, "1st Self-study Over")
        ringAt(18, 50, 0, 1, "2nd Self-study Begin")
        ringAt(19, 50, 0, 2, "2nd Self-study Over")
        ringAt(20, 0, 0, 4, "3rd Self-study Begin")
        ringAt(22, 0, 0, 3, "3rd Self-study Over")
        pyExecAt(22, 0, 30, "shutitdown()")
    else:
        infoLog("dailySchedule", "Today is Normal.")
        ringAt(7, 40, 0, 1, "1:Class Begin")
        ringAt(8, 20, 0, 2, "1:Class Over")
        ringAt(8, 30, 0, 1, "2:Class Begin")
        ringAt(9, 10, 0, 2, "2:Class Over")
        ringAt(9, 20, 0, 1, "3:Class Begin")
        ringAt(10, 0, 0, 2, "3:Class Over")
        ringAt(10, 30, 0, 1, "4:Class Begin")
        ringAt(11, 10, 0, 2, "4:Class Over")
        ringAt(11, 20, 0, 1, "5:Class Begin")
        ringAt(12, 0, 0, 2, "5:Class Over")
        ringAt(13, 40, 0, 1, "6:Class Begin")
        ringAt(14, 20, 0, 2, "6:Class Over")
        ringAt(14, 30, 0, 1, "7:Class Begin")
        ringAt(15, 10, 0, 2, "7:Class Over")
        ringAt(15, 40, 0, 1, "8:Class Begin")
        ringAt(17, 0, 0, 2, "9:Class Over")
        ringAt(17, 40, 0, 1, "1st Self-study Begin")
        ringAt(18, 40, 0, 2, "1st Self-study Over")
        ringAt(18, 50, 0, 1, "2nd Self-study Begin")
        ringAt(19, 50, 0, 2, "2nd Self-study Over")
        ringAt(20, 0, 0, 4, "3rd Self-study Begin")
        ringAt(22, 0, 0, 3, "3rd Self-study Over")
        pyExecAt(22, 0, 30, "shutitdown()")


if __name__ == "__main__":
    # 若直接运行
    # dailySchedule()
    argu = sys.argv
    infoLog("env", "Run with argv:"+str(argu))
    if "showmsg" in argu:
        infoLog("env", "showmsg")
        showTextScript()
    elif "run" in argu:
        infoLog("env", "run")
        dailySchedule()
    elif "autoconvert" in argu:
        infoLog("env", "autoconvert")
        autoConvertScript()
    elif "console" in argu:
        infoLog("env", "console")
        console()
    elif "help" in argu or "/help" in argu or "-h" in argu or "--help" in argu or "/?" in argu or "-?" in argu or "?" in argu or "--h" in argu or "-help" in argu or "/h" in argu:
        infoLog("env", "help")
        infoLog(
            "HELP", "Available Args:run/(blank or anything not mentioned below)/showmsg [text]/autoconvert/console/help")
    elif "zwt" in argu:
        infoLog("env", "zwt")
        infoLog("env", "Easter Egg Found!")
        zwt()
    else:
        infoLog("env", "dailySchedule")
        dailySchedule()


if __name__ != "__main__":
    # 若被import
    infoLog("env", "Imported")
    infoLog("env", "Hello from the AR Developer!")
    infoLog("HELP", "ringAt(hour,minute,second,stat,info)")
    infoLog("HELP", "stat:1-normBegin 2-normOver 3-AfterSchool 4-specBegin 5-specOver 6-NoSound")
    infoLog("HELP", "infoLog(origin,msg)")
    infoLog("HELP", "getSeconds(hour,minute,second)")
    infoLog("HELP", "playSound(filename)")
    infoLog("HELP", "pyExecAt(hour,minute,second,code)")
    infoLog("HELP", "showMsg(msg)")
    infoLog("HELP", "More functions available.Please read the source code.")


infoLog("env", "End of Script")

留下评论