This is a personal projects that aims to be the perfect online student. It auromatically joins zoom meetings, records classes and mirrors a pre-recorded video to a virtual loopback camera. Keep in mind that this is only meant to work on linux.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

136 lines
5.2 KiB

4 years ago
  1. import get_meetings
  2. import subprocess
  3. import datetime
  4. import os
  5. import json
  6. import time
  7. import sys
  8. from smb.SMBConnection import SMBConnection
  9. RECORD_PATH = "recordings"
  10. LISTENING_VIDEO_PATH = "listening.mkv"
  11. VIRTUAL_CAMERA_PATH = "/dev/video2"
  12. PULSEAUDIO_INDEX = 5
  13. X_OFFSET = sys.argv[1]
  14. PULSEAUDIO_INDEX = sys.argv[2]
  15. RECORD_TEMPLATE = ["ffmpeg", "-nostdin", "-video_size", "1920x1080", "-f", "x11grab", "-i", ":0.0+{},0".format(X_OFFSET), "-f", "pulse", "-ac", "2", "-i", str(PULSEAUDIO_INDEX), "-vcodec", "libx264", "-crf", "0", "-preset", "ultrafast", "-acodec", "pcm_s16le"]
  16. CAMERA_TEMPLATE = ["ffmpeg", "-re", "-i", LISTENING_VIDEO_PATH, "-map", "0:v", "-f", "v4l2", VIRTUAL_CAMERA_PATH]
  17. COMPRESSION_TEMPLATE = ["ffmpeg", "-i", None, "-vcodec", "libx265", "-crf", "28", None]
  18. if not os.path.exists(VIRTUAL_CAMERA_PATH):
  19. os.system("sudo modprobe -r v4l2loopback")
  20. os.system("sudo modprobe v4l2loopback")
  21. if not os.path.exists(RECORD_PATH):
  22. os.mkdir(RECORD_PATH)
  23. with open("config.json", "r") as f:
  24. config = json.loads(f.read())
  25. creds = config["creds"]
  26. backup_creds = config["backup"]
  27. next_day = datetime.datetime.today()
  28. def backup(directory):
  29. conn = SMBConnection(backup_creds["user"], backup_creds["password"],
  30. backup_creds["server_name"], backup_creds["server_name"], use_ntlm_v2 = True)
  31. conn.connect(backup_creds["ip"])
  32. files = conn.listPath(backup_creds["share"], "/")
  33. exists = False
  34. for f in files:
  35. if f.filename == backup_creds["path"]:
  36. if f.isDirectory:
  37. exists = True
  38. break
  39. else:
  40. conn.close()
  41. return 1
  42. if not exists:
  43. try:
  44. conn.createDirectory(backup_creds["share"],backup_creds["path"])
  45. except Exception as e:
  46. print(e)
  47. return 1
  48. folder = conn.getAttributes(backup_creds["share"], backup_creds["path"])
  49. if folder.isReadOnly:
  50. print("BACKUP PATH IS READONLY")
  51. return 1
  52. local_path = os.path.join(RECORD_PATH, directory)
  53. remote_path = os.path.join(backup_creds["path"], directory)
  54. conn.createDirectory(backup_creds["share"], remote_path)
  55. for i in os.listdir(local_path):
  56. with open(os.path.join(local_path, i), "rb") as f:
  57. conn.storeFile(backup_creds["share"],
  58. os.path.join(remote_path, i),
  59. f)
  60. conn.close()
  61. while True:
  62. now = datetime.datetime.today()
  63. while now < next_day:
  64. time.sleep(7200)
  65. now = datetime.datetime.today()
  66. print("NEW DAY")
  67. print("GENERATING MEETING LIST")
  68. meetings = get_meetings.get_meetings(creds["TC"], creds["passwd"])
  69. print("DONE\n")
  70. day = datetime.datetime.today().strftime("%d_%m_%Y")
  71. next_day = datetime.datetime.strptime("{} 00:00:00".format(day), '%d_%m_%Y %H:%M:%S') + datetime.timedelta(days = 1)
  72. if not os.path.exists(os.path.join(RECORD_PATH, day)):
  73. os.mkdir(os.path.join(RECORD_PATH, day))
  74. video_recording = None
  75. camera_mirroring = None
  76. k = None
  77. for k,i in enumerate(meetings):
  78. now = datetime.datetime.today()
  79. meeting_time = datetime.datetime.strptime("{} {}:00".format(day, i["time"]), '%d_%m_%Y %H:%M:%S')
  80. if video_recording:
  81. end_time = datetime.datetime.strptime("{} {}:00".format(day, meetings[k-1]["time"]), '%d_%m_%Y %H:%M:%S')+ datetime.timedelta(minutes = 30)
  82. while now < end_time:
  83. time.sleep(30)
  84. now = datetime.datetime.today()
  85. print("CLASS FINISHED, TERMINATING PREVIOUS RECORDINGS\n")
  86. video_recording.terminate()
  87. camera_mirroring.terminate()
  88. print(i["time"])
  89. print(i["class"])
  90. while now < meeting_time:
  91. time.sleep(30)
  92. now = datetime.datetime.today()
  93. print("STARTING")
  94. subprocess.Popen(["xdg-open", i["meeting_url"]], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
  95. cmd = RECORD_TEMPLATE + [os.path.join(RECORD_PATH, day, "{}.{}.mkv".format(k, i["class"]))]
  96. if os.path.exists(os.path.join(RECORD_PATH, day, "{}.{}.mkv".format(k, i["class"]))):
  97. os.remove(os.path.join(RECORD_PATH, day, "{}.{}.mkv".format(k, i["class"])))
  98. # print(" ".join(cmd))
  99. video_recording = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
  100. camera_mirroring = subprocess.Popen(CAMERA_TEMPLATE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
  101. if k:
  102. end_time = datetime.datetime.strptime("{} {}:00".format(day, meetings[k]["time"]), '%d_%m_%Y %H:%M:%S')+ datetime.timedelta(minutes = 30)
  103. while now < end_time:
  104. time.sleep(30)
  105. now = datetime.datetime.today()
  106. print("CLASS FINISHED, TERMINATING PREVIOUS RECORDINGS\n")
  107. video_recording.terminate()
  108. camera_mirroring.terminate()
  109. print("\nALL CLASSES RECORDED\n")
  110. todays_recordings_path = os.path.join(RECORD_PATH, day)
  111. recordings = os.listdir(todays_recordings_path)
  112. for r in recordings:
  113. COMPRESSION_TEMPLATE[2] = os.path.join(todays_recordings_path, r)
  114. COMPRESSION_TEMPLATE[-1] = os.path.join(todays_recordings_path, r[:-3] + ".mp4")
  115. print("COMPRESSING " + r)
  116. compression_process = subprocess.Popen(COMPRESSION_TEMPLATE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
  117. exit_code = compression_process.wait()
  118. print("FFMPEG EXITED WITH STATUS CODE {}".format(exit_code))
  119. if exit_code == 0:
  120. os.remove(COMPRESSION_TEMPLATE[2])